extends Node ## Shared headless client fixture for scene/skill-flow tests. ## ## It models the public M2Client surface used by GameScene, NetPlay and ## Quickbar. Quickslot mutations are server-confirmed immediately by ## emitting quickslots_changed; tests that need delayed confirmation should ## use their dedicated fixture and emit the signal manually. signal entity_spawned(entity: Dictionary) signal entity_despawned(vid: int) signal entity_moved(vid: int) signal entity_info(vid: int, entity: Dictionary) signal entity_main_set(vid: int) signal entity_dead(vid: int) signal vitals_changed(vid: int) signal point_changed(vid: int, point_type: int, value: int, amount: int) signal points_changed(points: Dictionary) signal target_info(vid: int, hp_percent: int) signal observer_mode_changed(enabled: bool) signal fishing_event(subheader: int, info: int, dir: int) signal chat(type: int, vid: int, text: String) signal damage(vid: int, amount: int, flag: int) signal motion(vid: int, victim_vid: int, motion: int) signal effect_cue(vid: int, name: String, special: int) signal emoticon_requested(index: int) signal gift_available() signal script_dialog(skin: int, text: String) signal affect_added(affect: Dictionary) signal affect_removed(type: int) signal guild_invite_ask(guild_id: int, guild_name: String, leader: String) signal guild_make_requested() signal warp(pos: Vector3, same_server: bool) signal world_reset() signal channel_changed(channel: int) signal disconnected(reason: String) signal entered_game() signal login_failed(reason: String) signal skills_changed() signal skill_group_changed(group: int) signal skill_cooldown_end(skill: int) signal quickslots_changed() var in_game := true var main := 1000 var entities: Dictionary = { 1000: {"vid": 1000, "name": "Me", "race": 0, "kind": 0, "ch_type": 0, "pos": Vector3.ZERO, "pos_cm": Vector3.ZERO, "moving_speed": 100, "attack_speed": 100, "hp": 100, "max_hp": 100, "dead": false} } var ground_items: Array = [] var inventory: Array = [] var equipment: Array = [] var affects: Array = [] var skills: Array = [{"id": 1, "level": 5, "master": 0}] var skill_group := 1 var points: Dictionary = { "level": 30, "skill_active": 5, "skill_support": 3, "skill_horse": 2, "casting_speed": 100, "hp": 100, "max_hp": 100, "sp": 100, "max_sp": 100, "exp": 0, "next_exp": 1000, } var target_vid := 0 var quickslots: Array = [] var calls: Array = [] func is_in_game() -> bool: return in_game func get_main_vid() -> int: return main func get_entity(vid: int) -> Dictionary: return entities.get(int(vid), {}) func get_entities() -> Array: return entities.values() func get_ground_items() -> Array: return ground_items func get_inventory() -> Array: return inventory func get_equipment() -> Array: return equipment func get_affects() -> Array: return affects func get_skills() -> Array: return skills func get_skill_group() -> int: return skill_group func get_points() -> Dictionary: return points func get_quickslots() -> Array: return quickslots func get_target() -> Dictionary: return {"vid": target_vid} func spawn(vid: int, name: String, pos: Vector3, is_main := false) -> void: var entity := {"vid": vid, "name": name, "race": 0, "kind": 2, "ch_type": 2, "pos": pos, "pos_cm": pos * 100.0, "moving_speed": 100, "attack_speed": 100, "hp": 100, "max_hp": 100, "dead": false, "is_main": is_main} entities[vid] = entity if is_main: main = vid entity_main_set.emit(vid) entity_spawned.emit(entity) func move(func_id: int, arg: int, rot: float, x: int, y: int) -> bool: calls.append(["move", func_id, arg, rot, x, y]) return in_game func attack(motion_id: int, victim: int) -> bool: calls.append(["attack", motion_id, victim]) return in_game func set_target(vid: int) -> bool: target_vid = vid calls.append(["set_target", vid]) return in_game func click_npc(vid: int) -> bool: calls.append(["click_npc", vid]) return in_game func pickup_item(vid: int) -> bool: calls.append(["pickup_item", vid]) return in_game func add_fly_targeting(vid: int, x: int, y: int) -> bool: calls.append(["add_fly_targeting", vid, x, y]) return in_game func fishing(rot_deg: float) -> bool: calls.append(["fishing", rot_deg]) return in_game func cast_skill(motion_id: int, rot: float, x: int, y: int, arg := 0) -> bool: calls.append(["cast_skill", motion_id, rot, x, y, arg]) return in_game func use_skill(skill_id: int, victim: int) -> bool: calls.append(["use_skill", skill_id, victim]) return in_game func use_item(window: int, cell: int) -> bool: calls.append(["use_item", window, cell]) return in_game func send_emoticon(index: int) -> bool: calls.append(["send_emoticon", index]) return in_game func say(type: int, text: String) -> bool: calls.append(["say", type, text]) return in_game func shoot(skill_id: int) -> bool: calls.append(["shoot", skill_id]) return in_game func skill_up(skill_id: int) -> bool: calls.append(["skill_up", skill_id]) return in_game func quickslot_add(pos: int, type: int, ref: int) -> bool: calls.append(["quickslot_add", pos, type, ref]) quickslots = quickslots.filter(func(q: Dictionary) -> bool: return int(q.get("pos", -1)) != pos) quickslots.append({"pos": pos, "type": type, "ref": ref}) quickslots_changed.emit() return in_game func quickslot_del(pos: int) -> bool: calls.append(["quickslot_del", pos]) quickslots = quickslots.filter(func(q: Dictionary) -> bool: return int(q.get("pos", -1)) != pos) quickslots_changed.emit() return in_game func quickslot_swap(pos: int, change_pos: int) -> bool: calls.append(["quickslot_swap", pos, change_pos]) var a := -1 var b := -1 for i in quickslots.size(): if int(quickslots[i].get("pos", -1)) == pos: a = i if int(quickslots[i].get("pos", -1)) == change_pos: b = i if a >= 0 and b >= 0: var tmp: Dictionary = quickslots[a] quickslots[a] = quickslots[b] quickslots[b] = tmp quickslots[a]["pos"] = pos quickslots[b]["pos"] = change_pos elif a >= 0: quickslots[a]["pos"] = change_pos elif b >= 0: quickslots[b]["pos"] = pos quickslots_changed.emit() return in_game func get_channel() -> int: return 1 func set_item_proto(_proto: Object) -> void: pass func get_item_proto(_vnum: int) -> Dictionary: return {} func guild_answer_invite(_guild_id: int, _accept: bool) -> bool: return in_game func guild_answer_make(_name: String) -> bool: return in_game