Skip to content

Commit b54a55a

Browse files
committed
added more consistent owner validity checks and owner passing checks for monster mobs
1 parent 9cb016d commit b54a55a

8 files changed

Lines changed: 210 additions & 84 deletions

File tree

broadcast/deploy-battle-nads.s.sol/10143/run-latest.json

Lines changed: 27 additions & 27 deletions
Large diffs are not rendered by default.

src/battle-nads/Balances.sol

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import { Errors } from "./libraries/Errors.sol";
88
import { Events } from "./libraries/Events.sol";
99
import { StatSheet } from "./libraries/StatSheet.sol";
1010
import { IShMonad } from "@fastlane-contracts/shmonad/interfaces/IShMonad.sol";
11-
1211
import { Instances } from "./Instances.sol";
1312

13+
import { SessionKey } from "lib/fastlane-contracts/src/common/relay/types/GasRelayTypes.sol";
14+
1415
// These are the entrypoint functions called by the tasks
1516
abstract contract Balances is GasRelayWithScheduling, Instances {
1617
using StatSheet for BattleNad;
@@ -156,6 +157,29 @@ abstract contract Balances is GasRelayWithScheduling, Instances {
156157
balances = balanceTracker;
157158
}
158159

160+
function _validatePersistOwnerDuringTask(
161+
BattleNad memory combatant,
162+
bool revertIfInvalid
163+
)
164+
internal
165+
returns (bool reschedule)
166+
{
167+
address _oldOwner = _abstractedMsgSender();
168+
SessionKey memory key = _loadSessionKey(msg.sender);
169+
if (key.expiration <= block.number) {
170+
revert Errors.InvalidCaller(msg.sender);
171+
}
172+
if (_oldOwner != combatant.owner || combatant.owner != key.owner) {
173+
if (revertIfInvalid) {
174+
revert Errors.InvalidCaller(msg.sender);
175+
}
176+
_deactivateSessionKey(msg.sender);
177+
_clearActiveTask(combatant.id);
178+
return false;
179+
}
180+
return true;
181+
}
182+
159183
function _getMinTaskReserveAmount() internal view returns (uint256 minBondedAmount) {
160184
minBondedAmount = TASK_COMMIT_RESERVE_FACTOR * _estimateTaskCost(block.number + SPAWN_DELAY, TASK_GAS);
161185
}

src/battle-nads/Character.sol

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -328,17 +328,21 @@ abstract contract Character is Abilities {
328328
if (combatant.stats.health > 3) {
329329
combatant = _outOfCombatStatUpdate(combatant);
330330

331-
combatant.activeTask.taskAddress = _loadActiveTaskAddress(combatant.id);
332-
if (_isValidAddress(combatant.activeTask.taskAddress)) {
333-
_clearKey(combatant, combatant.activeTask.taskAddress);
331+
if (!combatant.isMonster()) {
332+
combatant.activeTask.taskAddress = _loadActiveTaskAddress(combatant.id);
333+
if (_isValidAddress(combatant.activeTask.taskAddress)) {
334+
_clearKey(combatant, combatant.activeTask.taskAddress);
335+
}
336+
_clearActiveTask(combatant.id);
337+
combatant.activeTask.taskAddress = _EMPTY_ADDRESS;
338+
combatant.tracker.updateActiveTask = false;
339+
combatant = _checkClearAbility(combatant);
340+
}
341+
} else {
342+
if (!combatant.isMonster()) {
343+
combatant = _checkClearAbility(combatant);
334344
}
335-
_clearActiveTask(combatant.id);
336-
combatant.activeTask.taskAddress = _EMPTY_ADDRESS;
337-
combatant.tracker.updateActiveTask = false;
338345
}
339-
340-
combatant = _checkClearAbility(combatant);
341-
342346
return combatant;
343347
}
344348

src/battle-nads/Combat.sol

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,13 @@ abstract contract Combat is MonsterFactory {
148148
{
149149
if (attacker.isDead() || defender.isDead()) return false;
150150
if (attacker.stats.index == defender.stats.index) return false;
151-
if (defender.isMonster()) return true;
151+
if (defender.isMonster()) {
152+
if (!attacker.isMonster()) {
153+
return true;
154+
} else {
155+
return false;
156+
}
157+
}
152158
return attacker.stats.level + defender.stats.sumOfCombatantLevels <= (defender.stats.level * 2) + 1;
153159
}
154160

@@ -171,6 +177,9 @@ abstract contract Combat is MonsterFactory {
171177
uint256 attackerIndex = uint256(attacker.stats.index);
172178
uint256 targetIndex;
173179
uint256 targetBit;
180+
bool isBossEncounter = (excludedIndex != uint8(RESERVED_BOSS_INDEX))
181+
&& (attacker.stats.class != CharacterClass.Boss)
182+
&& (_isBoss(attacker.stats.depth, attacker.stats.x, attacker.stats.y));
174183

175184
// Sanity check against area bitmap
176185
uint256 areaBitmap = uint256(area.playerBitMap) | uint256(area.monsterBitMap);
@@ -181,7 +190,6 @@ abstract contract Combat is MonsterFactory {
181190
// Monsters can attack any player once they're aggro'd but not each other
182191
if (attacker.isMonster()) {
183192
combatantBitmap &= uint256(area.playerBitMap);
184-
combatantBitmap &= ~uint256(area.monsterBitMap);
185193
}
186194
// Remove any excluded index
187195
if (excludedIndex != 0) {
@@ -198,9 +206,18 @@ abstract contract Combat is MonsterFactory {
198206
}
199207

200208
targetIndex = uint256(attacker.stats.nextTargetIndex);
201-
if (targetIndex == attackerIndex) targetIndex = 0;
202-
if (targetIndex == 0) {
203-
targetIndex = attackerIndex == 1 ? 2 : 1;
209+
if (targetIndex < 2) {
210+
if (isBossEncounter) {
211+
targetIndex = 1; // uint8(RESERVED_BOSS_INDEX)
212+
} else {
213+
targetIndex = 2;
214+
}
215+
}
216+
217+
if (attackerIndex == targetIndex) {
218+
if (++targetIndex > 64) {
219+
targetIndex = isBossEncounter ? 1 : 2;
220+
}
204221
}
205222

206223
do {
@@ -268,7 +285,7 @@ abstract contract Combat is MonsterFactory {
268285
// Increment loop
269286
unchecked {
270287
if (++targetIndex > 64) {
271-
targetIndex = 1;
288+
targetIndex = isBossEncounter ? 1 : 2;
272289
}
273290
}
274291
} while (combatantBitmap != 0 && gasleft() > 110_000);

src/battle-nads/Constants.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ contract Constants {
6767
uint256 internal constant DEFAULT_AGGRO_CHANCE = 18; // out of 255 - spawns a new monster
6868
uint256 internal constant STARTING_OCCUPANT_THRESHOLD = 16;
6969
uint8 internal constant NO_COMBAT_ZONE_SPACING = 5;
70+
uint256 internal constant RESERVED_BOSS_INDEX = 1;
7071

7172
uint256 internal constant DAMAGE_DILUTION_FACTOR = 620;
7273
uint256 internal constant ABILITY_DILUTION_FACTOR = 780;

src/battle-nads/Handler.sol

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
pragma solidity 0.8.28;
33

44
import {
5-
BattleNad, BattleArea, StorageTracker, Inventory, BalanceTracker, Log, AbilityTracker, Ability
5+
BattleNad,
6+
BattleArea,
7+
StorageTracker,
8+
Inventory,
9+
BalanceTracker,
10+
Log,
11+
AbilityTracker,
12+
Ability,
13+
CharacterClass
614
} from "./Types.sol";
715

816
import { Balances } from "./Balances.sol";
@@ -152,6 +160,13 @@ abstract contract Handler is Balances {
152160

153161
// Flag for combat
154162
(monster, player) = _enterMutualCombatToTheDeath(monster, player);
163+
if (monster.isDead()) {
164+
player = _exitCombat(player);
165+
player.stats.nextTargetIndex = 0;
166+
player.tracker.updateStats = true;
167+
_storeArea(area, newDepth, newX, newY);
168+
return player;
169+
}
155170

156171
// Create tasks
157172
bool scheduledTask = false;
@@ -161,17 +176,20 @@ abstract contract Handler is Balances {
161176
uint256 targetBlock = block.number + _cooldown(monster.stats) + COMBAT_COLD_START_DELAY_MONSTER;
162177
(monster, scheduledTask) = _createOrRescheduleCombatTask(monster, targetBlock);
163178
if (!scheduledTask) {
179+
monster.owner = _EMPTY_ADDRESS;
180+
monster.tracker.updateOwner = true;
164181
emit Events.TaskNotScheduledInHandler(3, monster.id, block.number, targetBlock);
165182
}
166183
} else {
167184
// If task is no longer active, start a new one
168185
(scheduledTask,) = _checkClearTasks(monster);
169-
if (!scheduledTask) {
170-
monster.owner = player.owner;
171-
monster.tracker.updateOwner = true;
172-
uint256 targetBlock = block.number + _cooldown(monster.stats) + COMBAT_COLD_START_DELAY_MONSTER;
186+
if (!scheduledTask || !_isValidAddress(monster.owner)) {
187+
uint256 targetBlock = block.number + _cooldown(monster.stats);
173188
(monster, scheduledTask) = _createOrRescheduleCombatTask(monster, targetBlock);
174-
if (!scheduledTask) {
189+
if (scheduledTask) {
190+
monster.owner = player.owner;
191+
monster.tracker.updateOwner = true;
192+
} else {
175193
emit Events.TaskNotScheduledInHandler(4, monster.id, block.number, targetBlock);
176194
}
177195
}
@@ -281,6 +299,12 @@ abstract contract Handler is Balances {
281299
bytes32 defenderTargetID =
282300
areaCombatants[defender.stats.depth][defender.stats.x][defender.stats.y][defender.stats.nextTargetIndex];
283301
if (!_isValidID(defenderTargetID)) {
302+
uint256 defenderBitmap = uint256(defender.stats.combatantBitMap);
303+
uint256 targetBit = 1 << uint256(defender.stats.nextTargetIndex);
304+
if (defenderBitmap & targetBit != 0) {
305+
defenderBitmap &= ~targetBit;
306+
defender.stats.combatantBitMap = uint64(defenderBitmap);
307+
}
284308
defender.stats.nextTargetIndex = attacker.stats.index;
285309
defender.tracker.updateStats = true;
286310
}
@@ -292,21 +316,20 @@ abstract contract Handler is Balances {
292316
// Create tasks
293317
// Only create for attacker and defendant if tasks don't already exist
294318
(bool scheduledTask,) = _checkClearTasks(defender);
295-
if (!scheduledTask) {
296-
if (defender.isMonster()) {
297-
defender.owner = attacker.owner;
298-
defender.tracker.updateOwner = true;
299-
}
300-
(defender, scheduledTask) = _createOrRescheduleCombatTask(
301-
defender, block.number + _cooldown(defender.stats) + COMBAT_COLD_START_DELAY_DEFENDER
302-
);
303-
if (!scheduledTask) {
304-
emit Events.TaskNotScheduledInHandler(
305-
1,
306-
defender.id,
307-
block.number,
308-
block.number + _cooldown(defender.stats) + COMBAT_COLD_START_DELAY_DEFENDER
309-
);
319+
if (defender.isMonster()) {
320+
if (!scheduledTask || !_isValidAddress(defender.owner)) {
321+
if (!scheduledTask) {
322+
(defender, scheduledTask) =
323+
_createOrRescheduleCombatTask(defender, block.number + _cooldown(defender.stats));
324+
if (scheduledTask) {
325+
defender.owner = attacker.owner;
326+
defender.tracker.updateOwner = true;
327+
} else {
328+
emit Events.TaskNotScheduledInHandler(
329+
1, defender.id, block.number, block.number + _cooldown(defender.stats)
330+
);
331+
}
332+
}
310333
}
311334
}
312335

@@ -479,7 +502,9 @@ abstract contract Handler is Balances {
479502

480503
// CASE: No combatants remain
481504
if (!attacker.isInCombat()) {
482-
attacker = _checkClearAbility(attacker);
505+
if (!attacker.isMonster()) {
506+
attacker = _checkClearAbility(attacker);
507+
}
483508

484509
reschedule = false;
485510
nextExecutionBlock = 0;
@@ -505,14 +530,14 @@ abstract contract Handler is Balances {
505530
// Process attack
506531
(attacker, defender, log) = _attack(attacker, defender, log);
507532

508-
// If it's a monster, update defender's owner to most recent attacker
533+
// If it's a monster, update defender's owner to n
509534
// Only do this if there was a funding issue with prev task
535+
510536
if (defender.isMonster() && !attacker.isMonster()) {
511-
defender.owner = _loadOwner(defender.id);
512-
if (defender.owner != attacker.owner) {
537+
if (defender.owner != attacker.owner && _isValidAddress(defender.owner)) {
513538
(bool hasActiveCombatTask,) = _checkClearTasks(defender);
514539
if (!hasActiveCombatTask) {
515-
defender.owner = attacker.owner;
540+
defender.owner = _EMPTY_ADDRESS;
516541
defender.tracker.updateOwner = true;
517542
}
518543
}
@@ -718,6 +743,31 @@ abstract contract Handler is Balances {
718743
if (!attackerInCombat || !defenderInCombat) {
719744
(attacker, defender) = _enterMutualCombatToTheDeath(attacker, defender);
720745
}
746+
if (defender.isMonster() && !attacker.isMonster()) {
747+
if (!_isTask()) {
748+
(bool scheduledTask,) = _checkClearTasks(defender);
749+
if (!scheduledTask || !_isValidAddress(defender.owner)) {
750+
(defender, scheduledTask) =
751+
_createOrRescheduleCombatTask(defender, block.number + _cooldown(defender.stats));
752+
if (scheduledTask) {
753+
defender.owner = attacker.owner;
754+
defender.tracker.updateOwner = true;
755+
} else {
756+
emit Events.TaskNotScheduledInHandler(
757+
1, defender.id, block.number, block.number + _cooldown(defender.stats)
758+
);
759+
}
760+
}
761+
} else {
762+
if (defender.owner != attacker.owner && _isValidAddress(defender.owner)) {
763+
(bool hasActiveCombatTask,) = _checkClearTasks(defender);
764+
if (!hasActiveCombatTask) {
765+
defender.owner = _EMPTY_ADDRESS;
766+
defender.tracker.updateOwner = true;
767+
}
768+
}
769+
}
770+
}
721771
} else {
722772
if (attacker.activeAbility.targetIndex != 0 && attacker.activeAbility.ability != Ability.Pray) {
723773
revert Errors.AbilityCantHaveTarget();
@@ -794,6 +844,8 @@ abstract contract Handler is Balances {
794844
}
795845

796846
function _combatCheckLoop(BattleNad memory combatant, bool forceRemoveCombat) internal returns (BattleNad memory) {
847+
bool isBossEncounter = (combatant.stats.class != CharacterClass.Boss)
848+
&& (_isBoss(combatant.stats.depth, combatant.stats.x, combatant.stats.y));
797849
combatant.tracker.updateStats = true;
798850

799851
BattleArea memory area = _loadArea(combatant.stats.depth, combatant.stats.x, combatant.stats.y);
@@ -816,9 +868,18 @@ abstract contract Handler is Balances {
816868

817869
// Can't have an index of 0, start i at 1.
818870
uint256 targetIndex = uint256(combatant.stats.nextTargetIndex);
819-
if (targetIndex == uint256(combatant.stats.index)) targetIndex = 0;
820-
if (targetIndex == 0) {
821-
targetIndex = uint256(combatant.stats.index) == 1 ? 2 : 1;
871+
if (targetIndex < 2) {
872+
if (isBossEncounter) {
873+
targetIndex = 1; // uint8(RESERVED_BOSS_INDEX)
874+
} else {
875+
targetIndex = 2;
876+
}
877+
}
878+
879+
if (uint256(combatant.stats.index) == targetIndex) {
880+
if (++targetIndex > 64) {
881+
targetIndex = isBossEncounter ? 1 : 2;
882+
}
822883
}
823884

824885
while (gasleft() > 215_000 && combatantBitmap != 0) {
@@ -885,7 +946,7 @@ abstract contract Handler is Balances {
885946
// Increment loop
886947
unchecked {
887948
if (++targetIndex > 64) {
888-
targetIndex = 1;
949+
targetIndex = isBossEncounter ? 1 : 2;
889950
}
890951
}
891952
combatant.stats.nextTargetIndex = uint8(targetIndex);

0 commit comments

Comments
 (0)