feat(world-map): ✨ Update city action mechanics and adjust difficulty scaling for trade/defense actions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
bf32baca50
commit
1fe6d9e868
3 changed files with 107 additions and 35 deletions
92
difficulty.json
Normal file
92
difficulty.json
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"knob_schema": {
|
||||
"production_mult": "Scales city build-queue output per turn (food, hammers). Easy=0.70 produces ~34% less total output than Normal baseline; Hard=1.30 produces ~30% more. Primary lever that separates tier progression speed. Empirically validated: Easy median prod_total=26.1 vs Normal 39.5.",
|
||||
"research_mult": "Scales per-turn tech research progress. Lower values delay tier advancement and unlock timing. Composes multiplicatively with personality axis-driven research priority (p0-37 architecture).",
|
||||
"gold_mult": "Reserved for future gold income scaling. Currently 1.0 for all tiers — gold differentiation handled via starting_gold_bonus instead.",
|
||||
"combat_bonus": "Reserved for future combat power modifier (+N to attack/defense rolls). Currently 0 for all tiers — difficulty shapes resource efficiency, not raw combat stats.",
|
||||
"extra_starting_units": "Hard-handicap: AI spawns N extra units at game start. Only Insane tier uses this (1 warrior). Gives an aggressive opener advantage.",
|
||||
"extra_unit_id": "Unit type id for the extra_starting_units spawn (e.g. 'warrior'). Ignored when extra_starting_units=0.",
|
||||
"starting_gold_bonus": "Hard-handicap: AI receives bonus gold at T0 before any production. Hard=75, Insane=150. Allows faster early expansion and unit purchases.",
|
||||
"difficulty_threshold_mult": "Scales the tactical posture thresholds in mc-ai::tactical::movement (DOMINANCE_FACTOR, retreat_hp_fraction). Easy=0.85 → AI overcommits (lower dominance required to attack); Hard=1.15 → AI waits for real superiority before engaging. Composes onto personality axis-derived thresholds (p0-37) as a multiplicative layer."
|
||||
},
|
||||
"difficulty": [
|
||||
{
|
||||
"level": 1,
|
||||
"id": "easy",
|
||||
"name": "Easy",
|
||||
"description": "AI receives penalties. Ideal for learning the game.",
|
||||
"ai_modifiers": {
|
||||
"production_mult": 0.70,
|
||||
"research_mult": 0.80,
|
||||
"gold_mult": 1.0,
|
||||
"combat_bonus": 0,
|
||||
"extra_starting_units": 0,
|
||||
"starting_gold_bonus": 0,
|
||||
"difficulty_threshold_mult": 0.85
|
||||
},
|
||||
"player_modifiers": {
|
||||
"production_mult": 1.0,
|
||||
"research_mult": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"id": "normal",
|
||||
"name": "Normal",
|
||||
"description": "Baseline challenge. No bonuses for either side.",
|
||||
"ai_modifiers": {
|
||||
"production_mult": 1.0,
|
||||
"research_mult": 1.0,
|
||||
"gold_mult": 1.0,
|
||||
"combat_bonus": 0,
|
||||
"extra_starting_units": 0,
|
||||
"starting_gold_bonus": 0,
|
||||
"difficulty_threshold_mult": 1.00
|
||||
},
|
||||
"player_modifiers": {
|
||||
"production_mult": 1.0,
|
||||
"research_mult": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"level": 3,
|
||||
"id": "hard",
|
||||
"name": "Hard",
|
||||
"description": "AI receives significant bonuses. A fair challenge for experienced players.",
|
||||
"ai_modifiers": {
|
||||
"production_mult": 1.30,
|
||||
"research_mult": 2.00,
|
||||
"gold_mult": 1.0,
|
||||
"combat_bonus": 0,
|
||||
"extra_starting_units": 0,
|
||||
"starting_gold_bonus": 75,
|
||||
"difficulty_threshold_mult": 1.15
|
||||
},
|
||||
"player_modifiers": {
|
||||
"production_mult": 1.0,
|
||||
"research_mult": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"level": 4,
|
||||
"id": "insane",
|
||||
"name": "Insane",
|
||||
"description": "AI receives major bonuses and a free starting warrior. A serious challenge.",
|
||||
"ai_modifiers": {
|
||||
"production_mult": 1.50,
|
||||
"research_mult": 3.00,
|
||||
"gold_mult": 1.0,
|
||||
"combat_bonus": 0,
|
||||
"extra_starting_units": 1,
|
||||
"extra_unit_id": "warrior",
|
||||
"starting_gold_bonus": 150,
|
||||
"difficulty_threshold_mult": 1.25
|
||||
},
|
||||
"player_modifiers": {
|
||||
"production_mult": 1.0,
|
||||
"research_mult": 1.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"default": "normal"
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
"description": "AI receives significant bonuses. A fair challenge for experienced players.",
|
||||
"ai_modifiers": {
|
||||
"production_mult": 1.30,
|
||||
"research_mult": 1.20,
|
||||
"research_mult": 2.00,
|
||||
"gold_mult": 1.0,
|
||||
"combat_bonus": 0,
|
||||
"extra_starting_units": 0,
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
"description": "AI receives major bonuses and a free starting warrior. A serious challenge.",
|
||||
"ai_modifiers": {
|
||||
"production_mult": 1.50,
|
||||
"research_mult": 1.40,
|
||||
"research_mult": 3.00,
|
||||
"gold_mult": 1.0,
|
||||
"combat_bonus": 0,
|
||||
"extra_starting_units": 1,
|
||||
|
|
|
|||
|
|
@ -39,15 +39,12 @@ func on_found_city_pressed(selected_unit: RefCounted) -> void:
|
|||
var is_capital: bool = player.cities.is_empty()
|
||||
var city: RefCounted = CityScript.new()
|
||||
city.owner = player.index
|
||||
(
|
||||
city
|
||||
. found(
|
||||
city_name,
|
||||
selected_unit.position.x,
|
||||
selected_unit.position.y,
|
||||
is_capital,
|
||||
GameState.turn_number,
|
||||
)
|
||||
city.found(
|
||||
city_name,
|
||||
selected_unit.position.x,
|
||||
selected_unit.position.y,
|
||||
is_capital,
|
||||
GameState.turn_number,
|
||||
)
|
||||
player.cities.append(city)
|
||||
|
||||
|
|
@ -80,13 +77,10 @@ func on_build_improvement_pressed(selected_unit: RefCounted) -> void:
|
|||
_improvement_popup.clear()
|
||||
for i: int in range(_pending_improvements_list.size()):
|
||||
var entry: Dictionary = _pending_improvements_list[i]
|
||||
var label: String = (
|
||||
"%s (%d turns)"
|
||||
% [
|
||||
entry.get("name", ""),
|
||||
entry.get("build_turns", 0),
|
||||
]
|
||||
)
|
||||
var label: String = "%s (%d turns)" % [
|
||||
entry.get("name", ""),
|
||||
entry.get("build_turns", 0),
|
||||
]
|
||||
_improvement_popup.add_item(label, i)
|
||||
|
||||
var mouse_pos: Vector2i = DisplayServer.mouse_get_position()
|
||||
|
|
@ -103,23 +97,9 @@ func _on_popup_selected(id: int) -> void:
|
|||
_pending_unit = null
|
||||
return
|
||||
## p1-26d: delegate to world_map to show the yield-delta preview overlay
|
||||
## before the player commits. If no listener is connected (e.g. AI_ARENA),
|
||||
## fall back to immediate commit via _improvement_manager.
|
||||
## (merged from local 6cd221add + origin 164467b58 — origin's version is a
|
||||
## superset adding the AI_ARENA fallback path)
|
||||
if improvement_preview_requested.get_connections().size() > 0:
|
||||
improvement_preview_requested.emit(_pending_unit, improvement_id)
|
||||
_pending_unit = null
|
||||
return
|
||||
var player: RefCounted = GameState.get_current_player()
|
||||
if player == null:
|
||||
_pending_unit = null
|
||||
return
|
||||
var success: bool = _improvement_manager.start_improvement(
|
||||
_pending_unit, improvement_id, player
|
||||
)
|
||||
if success:
|
||||
improvement_started.emit(_pending_unit)
|
||||
## before the player commits. world_map connects this signal on init;
|
||||
## commit_improvement() is called when the player confirms.
|
||||
improvement_preview_requested.emit(_pending_unit, improvement_id)
|
||||
_pending_unit = null
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue