This commit is contained in:
team3
2026-09-16 13:21:45 +02:00
parent 8a188c5d5c
commit 49e8f2d95e
27 changed files with 1772 additions and 265 deletions

View File

@@ -27,7 +27,11 @@ var human_recap: Dictionary = {} ## Rückblick auf den letzten eigenen Kampf
var human_last_result: int = 0 ## 1 Sieg, -1 Niederlage, 0 unentschieden/keine
func start(seed_value: int = 0) -> void:
var difficulty: int = Data.Difficulty.NORMAL
func start(seed_value: int = 0, p_difficulty: int = Data.Difficulty.NORMAL) -> void:
difficulty = p_difficulty
rand.seed = seed_value if seed_value != 0 else randi()
pool = Pool.new()
players.clear()
@@ -37,7 +41,7 @@ func start(seed_value: int = 0) -> void:
var b := PlayerState.new(i + 1, BOT_NAMES[i], true)
players.append(b)
var ai := BotAI.new()
ai.setup(rand)
ai.setup(rand, difficulty)
bots[b.pid] = ai
round_no = 1
for p in players:
@@ -61,8 +65,9 @@ func alive_players() -> Array:
func _run_bot_turns() -> void:
for p in players:
if p.is_bot and p.alive:
bots[p.pid].take_turn(p, pool, rand)
bots[p.pid].arrange(p, opponent_of(p))
var opp := opponent_of(p)
bots[p.pid].take_turn(p, pool, rand, opp)
bots[p.pid].arrange(p, opp)
## Planungsuhr. Läuft sie ab, startet der Kampf von selbst.
@@ -85,7 +90,7 @@ func start_combat() -> void:
return
# Abbilder erst jetzt kopieren, damit sie den aktuellen Stand zeigen.
for pr in pairings:
if pr["ghost"]:
if pr["ghost"] and pr["ghost_src"] != null:
pr["b"] = _make_ghost(pr["ghost_src"])
pending.clear()
player_combat = null
@@ -117,10 +122,28 @@ func skip_combat() -> void:
resolve_round()
func is_pve_round() -> bool:
return round_no <= Data.PVE_ROUNDS
## Wildnis-Gegner: klein, ohne Leben, jeder Spieler bekommt seinen eigenen.
func _make_creeps() -> PlayerState:
var c := PlayerState.new(-2, "Wildnis", true)
c.level = 9
c.board[Vector2i(3, 0)] = Avatar.new(Data.Role.TANK, [Data.Element.ERDE] as Array[int], 1)
if round_no >= 2:
c.board[Vector2i(2, 1)] = Avatar.new(Data.Role.DPS, [Data.Element.WIND] as Array[int], 1)
return c
func _make_pairings() -> Array:
var alive := alive_players()
alive.shuffle()
var out := []
if is_pve_round():
for p in alive:
out.append({"a": p, "b": _make_creeps(), "ghost": true, "ghost_src": null})
return out
alive.shuffle()
var i := 0
while i + 1 < alive.size():
out.append({"a": alive[i], "b": alive[i + 1], "ghost": false})
@@ -207,7 +230,9 @@ func _next_round() -> void:
for p in players:
if not p.alive:
continue
p.gold += p.round_income()
var income: int = p.round_income()
p.gold += income
p.stats["gold_earned"] += income
p.gain_xp(Data.XP_PER_ROUND)
p.new_round_shop(pool, rand)
pairings = _make_pairings()
@@ -216,13 +241,96 @@ func _next_round() -> void:
phase = Phase.PLANNING
# --- Speichern -------------------------------------------------------------
func to_dict() -> Dictionary:
var ps := []
for p in players:
ps.append(p.to_dict())
var ai := {}
for pid in bots:
ai[str(pid)] = {"pref": Array(bots[pid].pref), "greed": bots[pid].greed}
return {
"version": 1, "round": round_no, "difficulty": difficulty, "rng_state": str(rand.state),
"pool": Array(pool.element_left), "players": ps, "bots": ai,
"human_recap": human_recap, "log": last_round_log,
}
func from_dict(d: Dictionary) -> void:
difficulty = int(d["difficulty"])
rand.state = int(str(d["rng_state"]))
pool = Pool.new()
for i in mini(pool.element_left.size(), d["pool"].size()):
pool.element_left[i] = int(d["pool"][i])
players.clear()
bots.clear()
for pd in d["players"]:
var p := PlayerState.from_dict(pd)
players.append(p)
if not p.is_bot:
human = p
else:
var ai := BotAI.new()
ai.setup(rand, difficulty)
var bd: Dictionary = d["bots"].get(str(p.pid), {})
if bd.has("pref"):
ai.pref.clear()
for e in bd["pref"]:
ai.pref.append(int(e))
ai.greed = float(bd["greed"])
bots[p.pid] = ai
round_no = int(d["round"])
human_recap = d.get("human_recap", {})
last_round_log = d.get("log", [])
pairings = _make_pairings()
for p in players:
if p.is_bot and p.alive:
bots[p.pid].arrange(p, opponent_of(p))
plan_left = Data.PLAN_SECONDS
phase = Phase.PLANNING
func save(path: String = Data.SAVE_PATH) -> bool:
if phase != Phase.PLANNING:
return false
var f := FileAccess.open(path, FileAccess.WRITE)
if f == null:
return false
f.store_string(JSON.stringify(to_dict()))
return true
static func has_save(path: String = Data.SAVE_PATH) -> bool:
return FileAccess.file_exists(path)
static func delete_save(path: String = Data.SAVE_PATH) -> void:
if FileAccess.file_exists(path):
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
static func load_from(path: String = Data.SAVE_PATH) -> Game:
if not FileAccess.file_exists(path):
return null
var f := FileAccess.open(path, FileAccess.READ)
if f == null:
return null
var parsed = JSON.parse_string(f.get_as_text())
if not (parsed is Dictionary):
return null
var g := Game.new()
g.from_dict(parsed)
return g
# --- Hilfen für die UI ----------------------------------------------------
## Gegner eines Spielers in der aktuellen Paarung (Abbilder liefern ihre Quelle).
func opponent_of(p: PlayerState) -> PlayerState:
for pr in pairings:
if pr["a"] == p:
return pr["ghost_src"] if pr["ghost"] else pr["b"]
return pr["ghost_src"] if (pr["ghost"] and pr["ghost_src"] != null) else pr["b"]
if pr["b"] == p:
return pr["a"]
return null