Health survives a door, and experience needs you to be there
Arriving anywhere set health to maximum, so a run cost nothing: walk in, take what you could, walk out, walk back in whole. Current health now lives on the character record for the same reason the inventory does -- SimWorld builds a blank player on every instance change, so anything that must survive a door has to be written down -- and it is banked at every transfer, every disconnect, and on every item transaction, since a potion moves an item and hit points in one action. The hub is where that damage is paid off, at 10% of maximum per second: about ten seconds from nothing, fast enough not to be a chore and slow enough to be a decision before walking back in. The dungeon rate is unchanged, so waiting one out is still not a plan. Regeneration is a property of the world now rather than a constant. adopt() no longer touches current health. It is called mid-session by on_choose_upgrade, and reading health from the record there would snap a player back to whatever was last written to disk; recompute_max_hp still clamps downwards so Glass Cannon takes the hit points above the new ceiling with it. ServerRuntime._adopt_character stays the one place a record becomes a live player. Experience is still undivided, but now only among players within XP_AWARD_RADIUS of the kill -- ten percent past what a player can see, so anyone who watched it die is inside and someone parked in the entrance room is not. The death events carry a position for this, and deliberately do not put it on the wire: encode_events still writes the id alone. diag_progression covers the whole chain end to end -- wounded in a dungeon, escaped to the hub with the wound intact, healing there, and walking back in still hurt -- because a unit test cannot transfer a peer. Both halves were mutation-tested against it; the unit suite alone missed the arrival heal entirely. The Proving Grounds is recorded in the roadmap as a debugging tool that does not ship: it pays full experience for a boss with 8% health. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
54086b5604
commit
4aeb6d229d
13 files changed
+405
-28
No files matched your search
@@ -18,6 +18,8 @@ var _step: int = 0
|
||||
var _srv: ServerRuntime
|
||||
var _account: int = 424242
|
||||
var _character: Character
|
||||
var _wounded: int = 0
|
||||
var _hub_arrival: int = 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -77,6 +79,11 @@ func _physics_process(_delta: float) -> void:
|
||||
_check(false, "the dungeon had a living enemy to kill")
|
||||
_finish()
|
||||
return
|
||||
# Stand over it. Experience now has a radius, so a kill the player is
|
||||
# nowhere near pays nothing -- which would make this check depend on
|
||||
# where the generator happened to put the enemy.
|
||||
var mine_now: SimPlayer = inst.world.players[Net.LOCAL_PEER]
|
||||
mine_now.pos = victim.pos
|
||||
victim.hp = 1
|
||||
inst.world._damage_enemy(victim, 100)
|
||||
_srv._dispatch_events(inst)
|
||||
@@ -116,6 +123,68 @@ func _physics_process(_delta: float) -> void:
|
||||
_check(_srv.store.characters_for(_account).size() == before_count,
|
||||
"creating a character inside a dungeon is refused too")
|
||||
|
||||
elif _step == 65:
|
||||
# A kill nobody was near pays nobody. The radius exists because without
|
||||
# it a player parked in the entrance room collected the full experience
|
||||
# for a boss they never saw.
|
||||
var inst := _srv.instance_of(Net.LOCAL_PEER)
|
||||
var far: SimEnemy = null
|
||||
for e in inst.world.enemies.values():
|
||||
if e.alive:
|
||||
far = e
|
||||
break
|
||||
if far == null:
|
||||
_check(false, "a second enemy to kill out of sight")
|
||||
else:
|
||||
var p: SimPlayer = inst.world.players[Net.LOCAL_PEER]
|
||||
p.pos = far.pos + Vector2(SimConfig.XP_AWARD_RADIUS + 400.0, 0.0)
|
||||
var before: int = _srv.store.get_character(_account, _character.id).total_xp
|
||||
far.hp = 1
|
||||
inst.world._damage_enemy(far, 100)
|
||||
_srv._dispatch_events(inst)
|
||||
var after: int = _srv.store.get_character(_account, _character.id).total_xp
|
||||
_check(after == before,
|
||||
"a kill out of range awards nothing (%d -> %d)" % [before, after])
|
||||
|
||||
elif _step == 82:
|
||||
# Health is carried, not reset. Arriving anywhere used to set it to
|
||||
# maximum, which made a run free: walk in, take what you can, walk out,
|
||||
# walk back in whole.
|
||||
var inst := _srv.instance_of(Net.LOCAL_PEER)
|
||||
var p: SimPlayer = inst.world.players[Net.LOCAL_PEER]
|
||||
p.spawn_grace = 0
|
||||
p.hp = int(float(p.max_hp) * 0.4)
|
||||
_wounded = p.hp
|
||||
_check(_wounded > 0 and _wounded < p.max_hp, "the player is wounded")
|
||||
|
||||
elif _step == 84:
|
||||
_srv._send_to_lobby(Net.LOCAL_PEER)
|
||||
var inst := _srv.instance_of(Net.LOCAL_PEER)
|
||||
_check(inst != null and inst.kind == Protocol.InstanceKind.LOBBY,
|
||||
"escaped to the hub")
|
||||
var p: SimPlayer = inst.world.players[Net.LOCAL_PEER]
|
||||
_check(p.hp < p.max_hp, "the wound came with them (%d/%d)" % [p.hp, p.max_hp])
|
||||
_check(absi(p.hp - _wounded) <= 2,
|
||||
"and it is the same wound, not a new number (%d vs %d)" % [p.hp, _wounded])
|
||||
_hub_arrival = p.hp
|
||||
|
||||
elif _step == 88:
|
||||
var inst := _srv.instance_of(Net.LOCAL_PEER)
|
||||
var p: SimPlayer = inst.world.players[Net.LOCAL_PEER]
|
||||
_check(p.hp > _hub_arrival,
|
||||
"the hub is healing it back (%d -> %d)" % [_hub_arrival, p.hp])
|
||||
_check(p.hp < p.max_hp, "but has not finished in four ticks")
|
||||
_srv._send_to_dungeon(Net.LOCAL_PEER)
|
||||
|
||||
elif _step == 89:
|
||||
var inst := _srv.instance_of(Net.LOCAL_PEER)
|
||||
_check(inst != null and inst.kind == Protocol.InstanceKind.DUNGEON,
|
||||
"back into a dungeon")
|
||||
var p: SimPlayer = inst.world.players[Net.LOCAL_PEER]
|
||||
_check(p.hp < p.max_hp,
|
||||
"and walked in still hurt (%d/%d) -- entering is not a heal"
|
||||
% [p.hp, p.max_hp])
|
||||
|
||||
elif _step == 90:
|
||||
# Stop behaving like a bot before the kill. A bot auto-creates a
|
||||
# replacement the instant it sees an empty roster -- correct in play,
|
||||
|
||||
Reference in new issue
Block a user