Hauptszene scenes/luneraria.tscn, Saumwacht bleibt über --legacy und die alten QA-Schalter erreichbar. Karte aus Bestandsassets plus neuem Turm, HUD mit Planung, Lanes und Minimap, Menü umbenannt, Einstellungen werden aus dem alten Benutzerordner übernommen. Simulation 6,6x schneller (Raster, Caches, faire Staffelung) und exakt spiegelsymmetrisch. Regeln 91/91, echte Anwendung 38/38, Legacy unverändert grün. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
3.8 KiB
GDScript
83 lines
3.8 KiB
GDScript
class_name LuneBot
|
|
extends RefCounted
|
|
## Plans through LuneMatch.submit exactly like the human: same costs, same caps, same phase.
|
|
## Reads only public state (committed buildings, round) and its own draft, never other drafts.
|
|
|
|
const PROFILES: Dictionary = {
|
|
"ausgewogen": {"front": .30, "melee": .25, "ranged": .25, "splash": .10, "support": .10, "upgrade_from": 6, "support_lane": 0.0},
|
|
"verteidigung": {"front": .40, "melee": .15, "ranged": .20, "splash": .10, "support": .15, "upgrade_from": 5, "support_lane": 0.0},
|
|
"druck": {"front": .20, "melee": .35, "ranged": .25, "splash": .15, "support": .05, "upgrade_from": 8, "support_lane": 0.0},
|
|
"fernkampf": {"front": .25, "melee": .10, "ranged": .40, "splash": .15, "support": .10, "upgrade_from": 6, "support_lane": 0.0},
|
|
"teamhilfe": {"front": .30, "melee": .20, "ranged": .25, "splash": .10, "support": .15, "upgrade_from": 6, "support_lane": .35},
|
|
}
|
|
|
|
static func profile_for(match_state: LuneMatch, slot: int) -> String:
|
|
var names: Array = PROFILES.keys()
|
|
return names[(slot * 7 + match_state.seed_value) % names.size()]
|
|
|
|
static func plan(match_state: LuneMatch, slot: int, profile_name: String = "") -> void:
|
|
var name: String = profile_name if not profile_name.is_empty() else profile_for(match_state, slot)
|
|
var profile: Dictionary = PROFILES[name]
|
|
var player: Dictionary = match_state.players[slot]
|
|
var ids: Array[String] = match_state.base_ids(player.bund)
|
|
var roles: Dictionary = {}
|
|
for id: String in ids:
|
|
roles[match_state.role_of(id)] = id
|
|
for guard: int in range(40):
|
|
var draft: Dictionary = player.draft
|
|
var roster: Array = draft.roster
|
|
var acted: bool = false
|
|
if roster.size() >= int(profile.upgrade_from) or roster.size() >= match_state.balance.supply_cap:
|
|
acted = _try_upgrade(match_state, slot, roster)
|
|
if not acted and roster.size() < match_state.balance.supply_cap:
|
|
var role: String = _needed_role(match_state, roster, profile)
|
|
var lane: String = player.lane
|
|
if float(profile.support_lane) > 0.0 and match_state.rng.randf() < float(profile.support_lane):
|
|
lane = _weakest_lane(match_state, int(player.team))
|
|
acted = match_state.submit(slot, {"type": "buy", "type_id": roles[role], "lane": lane}).ok
|
|
if not acted and roster.size() < int(profile.upgrade_from):
|
|
acted = _try_upgrade(match_state, slot, roster)
|
|
if not acted:
|
|
break
|
|
match_state.set_ready(slot)
|
|
|
|
static func _needed_role(match_state: LuneMatch, roster: Array, profile: Dictionary) -> String:
|
|
var counts: Dictionary = {}
|
|
for entry: Dictionary in roster:
|
|
var role: String = match_state.role_of(entry.type_id)
|
|
counts[role] = int(counts.get(role, 0)) + 1
|
|
var best: String = "front"
|
|
var best_gap: float = -INF
|
|
for role: String in LuneMatch.ROW_ORDER:
|
|
var gap: float = float(profile[role]) * float(roster.size() + 1) - float(counts.get(role, 0))
|
|
if gap > best_gap + 0.0001:
|
|
best = role
|
|
best_gap = gap
|
|
return best
|
|
|
|
static func _try_upgrade(match_state: LuneMatch, slot: int, roster: Array) -> bool:
|
|
var best_uid: String = ""
|
|
var best_seq: int = 1 << 30
|
|
var best_cost: int = 1 << 30
|
|
for entry: Dictionary in roster:
|
|
var upgrade: String = str(DataCatalog.get_unit(entry.type_id).get("upgrade_id", ""))
|
|
if upgrade.is_empty():
|
|
continue
|
|
var cost: int = int(DataCatalog.get_unit(upgrade).cost)
|
|
if cost < best_cost or (cost == best_cost and int(entry.seq) < best_seq):
|
|
best_cost = cost
|
|
best_seq = int(entry.seq)
|
|
best_uid = str(entry.uid)
|
|
if best_uid.is_empty():
|
|
return false
|
|
return match_state.submit(slot, {"type": "upgrade", "uid": best_uid}).ok
|
|
|
|
static func _weakest_lane(match_state: LuneMatch, team: int) -> String:
|
|
var weakest: String = "mid"
|
|
var fewest: int = 99
|
|
for lane: String in LuneMap.LANES:
|
|
var towers: int = match_state.towers_alive(team, lane)
|
|
if towers < fewest:
|
|
fewest = towers
|
|
weakest = lane
|
|
return weakest
|