refactor: rename hammer→production throughout codebase
Production is production, hammers are weapons. Single source of truth. Rust: hammer_cost→production_cost, hammers_invested→production_invested, hammers params→production (with serde aliases for JSON backward compat) GDScript: hammers vars→prod, comments updated Guide: "Hammers" label→"Production" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d67d94e440
commit
0dc1fa5db8
7 changed files with 64 additions and 59 deletions
|
|
@ -43,7 +43,7 @@ var owner_index: int = -1
|
|||
## Each entry: {type: "building"|"unit", id: String, cost: int}
|
||||
var production_queue: Array = []
|
||||
|
||||
## Accumulated hammers toward the current production_queue[0].
|
||||
## Accumulated production toward the current production_queue[0].
|
||||
var production_progress: int = 0
|
||||
|
||||
# ── Property accessors (used by renderers) ─────────────────────────
|
||||
|
|
@ -249,10 +249,10 @@ func process_culture(tile_yields_json: String) -> bool:
|
|||
return _gd_city.call("process_culture", tile_yields_json)
|
||||
|
||||
|
||||
## Add production hammers.
|
||||
func add_production(hammers: float) -> void:
|
||||
## Add production points to the city's accumulator.
|
||||
func add_production(production: float) -> void:
|
||||
if _gd_city != null:
|
||||
_gd_city.call("add_production", hammers)
|
||||
_gd_city.call("add_production", production)
|
||||
|
||||
|
||||
## Get food surplus (food yield - consumption).
|
||||
|
|
@ -312,12 +312,12 @@ func add_to_queue(type: String, item_id: String) -> void:
|
|||
production_queue.append({"type": type, "id": item_id, "cost": cost})
|
||||
|
||||
|
||||
## Apply hammers to the building/unit production queue. Returns
|
||||
## true if the current item completed this tick.
|
||||
func apply_production(hammers: int) -> bool:
|
||||
## Apply production to the building/unit queue. Returns true if the
|
||||
## current item completed this tick.
|
||||
func apply_production(production: int) -> bool:
|
||||
if production_queue.is_empty():
|
||||
return false
|
||||
production_progress += hammers
|
||||
production_progress += production
|
||||
var current: Dictionary = production_queue[0]
|
||||
var cost: int = int(current.get("cost", 0))
|
||||
if production_progress >= cost:
|
||||
|
|
@ -370,14 +370,14 @@ func enqueue_item(
|
|||
## Tick a building's queue. Returns number of completed items.
|
||||
func tick_building(
|
||||
building: String,
|
||||
hammers: int,
|
||||
production: int,
|
||||
treasury: RefCounted = null
|
||||
) -> int:
|
||||
if _gd_city == null:
|
||||
_warn_missing_extension()
|
||||
return 0
|
||||
var completed_ids: PackedStringArray = _gd_city.call(
|
||||
"tick_building", building, hammers
|
||||
"tick_building", building, production
|
||||
)
|
||||
var count: int = completed_ids.size()
|
||||
if count == 0:
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ func _process_production(player: RefCounted) -> void: # Player
|
|||
var c: CityScript = city_ref as CityScript
|
||||
var tile_json: String = BuildableHelperScript.build_tile_yields_json(c, game_map)
|
||||
var yields: Dictionary = c.get_yields(tile_json)
|
||||
var hammers: int = int(yields.get("production", 1) * prod_modifier)
|
||||
var prod: int = int(yields.get("production", 1) * prod_modifier)
|
||||
# Capture current item before apply_production pops it on completion.
|
||||
var current: Dictionary = (
|
||||
c.production_queue.front() as Dictionary
|
||||
if not c.production_queue.is_empty()
|
||||
else {}
|
||||
)
|
||||
if not c.apply_production(hammers):
|
||||
if not c.apply_production(prod):
|
||||
continue
|
||||
var item_type: String = current.get("type", "")
|
||||
var item_id: String = current.get("id", "")
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ static func process_production(
|
|||
var c: CityScript = city as CityScript
|
||||
var tile_json: String = build_tile_yields_json(c, game_map)
|
||||
var yields: Dictionary = c.get_yields(tile_json)
|
||||
var hammers: int = int(yields.get("production", 1) * prod_modifier)
|
||||
var prod: int = int(yields.get("production", 1) * prod_modifier)
|
||||
# Capture current item before apply_production pops it on completion.
|
||||
var current: Dictionary = (
|
||||
c.production_queue.front() as Dictionary
|
||||
if not c.production_queue.is_empty()
|
||||
else {}
|
||||
)
|
||||
if not c.apply_production(hammers):
|
||||
if not c.apply_production(prod):
|
||||
continue
|
||||
var item_type: String = current.get("type", "")
|
||||
var item_id: String = current.get("id", "")
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ func test_happy_path_enqueue_tick_emits_item_crafted() -> void:
|
|||
|
||||
var treasury: RefCounted = ClassDB.instantiate("GdTreasury") as RefCounted
|
||||
var completed: int = city.tick_building("smithy", 30, treasury)
|
||||
assert_eq(completed, 1, "30 hammers should complete iron_axe")
|
||||
assert_eq(completed, 1, "30 production should complete iron_axe")
|
||||
assert_eq(city.queue_len("smithy"), 0)
|
||||
|
||||
assert_eq(_crafted_events.size(), 1, "item_crafted should fire exactly once")
|
||||
|
|
|
|||
|
|
@ -819,7 +819,7 @@ impl GdCity {
|
|||
}
|
||||
|
||||
/// Load items from a JSON array `[ { id, production: { building, secondary_building?,
|
||||
/// hammer_cost, materials?, requires_tech? }, ... }, ... ]`. The DataLoader passes
|
||||
/// production_cost, materials?, requires_tech? }, ... }, ... ]`. The DataLoader passes
|
||||
/// the merged item list from `public/resources/items/*.json`. Items without a
|
||||
/// `production` block (apex relics) are skipped.
|
||||
#[func]
|
||||
|
|
@ -829,7 +829,7 @@ impl GdCity {
|
|||
building: String,
|
||||
#[serde(default)]
|
||||
secondary_building: Option<String>,
|
||||
hammer_cost: u32,
|
||||
production_cost: u32,
|
||||
#[serde(default)]
|
||||
materials: Vec<mc_city::MaterialCost>,
|
||||
#[serde(default)]
|
||||
|
|
@ -852,7 +852,7 @@ impl GdCity {
|
|||
let Some(p) = doc.production else { continue };
|
||||
self.item_registry.insert(mc_city::ItemDef {
|
||||
id: doc.id,
|
||||
hammer_cost: p.hammer_cost,
|
||||
production_cost: p.production_cost,
|
||||
building: p.building,
|
||||
secondary_building: p.secondary_building,
|
||||
requires_tech: p.requires_tech,
|
||||
|
|
@ -895,13 +895,13 @@ impl GdCity {
|
|||
}
|
||||
}
|
||||
|
||||
/// Apply `hammers` to one building's queue. Returns an Array of completed
|
||||
/// Apply `production` to one building's queue. Returns an Array of completed
|
||||
/// item ids (in completion order). The caller should add each to the
|
||||
/// civ Treasury and emit `EventBus.item_crafted(item_id, city, player)`.
|
||||
/// Returns an empty array on error and logs to godot_error.
|
||||
#[func]
|
||||
fn tick_building(&mut self, building: GString, hammers: i64) -> PackedStringArray {
|
||||
let h = hammers.max(0) as u32;
|
||||
fn tick_building(&mut self, building: GString, production: i64) -> PackedStringArray {
|
||||
let h = production.max(0) as u32;
|
||||
match self.inner.tick_building(&building.to_string(), h) {
|
||||
Ok(completed) => {
|
||||
let mut out = PackedStringArray::new();
|
||||
|
|
@ -928,7 +928,7 @@ impl GdCity {
|
|||
}
|
||||
|
||||
/// Snapshot a building's queue as an Array of Dictionaries:
|
||||
/// `[{ item_id, hammer_cost, hammers_invested, progress }, ...]`
|
||||
/// `[{ item_id, production_cost, production_invested, progress }, ...]`
|
||||
#[func]
|
||||
fn queue_snapshot(&self, building: GString) -> Array<Dictionary> {
|
||||
let mut out = Array::<Dictionary>::new();
|
||||
|
|
@ -939,8 +939,8 @@ impl GdCity {
|
|||
let mut d = Dictionary::new();
|
||||
let mc_city::Queueable::Item { item_id } = &entry.queueable;
|
||||
d.set("item_id", GString::from(item_id));
|
||||
d.set("hammer_cost", entry.hammer_cost as i64);
|
||||
d.set("hammers_invested", entry.hammers_invested as i64);
|
||||
d.set("production_cost", entry.production_cost as i64);
|
||||
d.set("production_invested", entry.production_invested as i64);
|
||||
d.set("progress", entry.progress() as f64);
|
||||
out.push(&d);
|
||||
}
|
||||
|
|
@ -1157,10 +1157,10 @@ impl GdCity {
|
|||
self.inner.process_culture(&ty)
|
||||
}
|
||||
|
||||
/// Add production hammers.
|
||||
/// Add production production.
|
||||
#[func]
|
||||
fn add_production(&mut self, hammers: f64) {
|
||||
self.inner.add_production(hammers);
|
||||
fn add_production(&mut self, production: f64) {
|
||||
self.inner.add_production(production);
|
||||
}
|
||||
|
||||
/// Get food surplus for this turn (food yield - consumption).
|
||||
|
|
|
|||
|
|
@ -358,9 +358,9 @@ impl City {
|
|||
self.can_expand()
|
||||
}
|
||||
|
||||
/// Add production hammers (from the city's production yield).
|
||||
pub fn add_production(&mut self, hammers: f64) {
|
||||
self.production_progress += hammers;
|
||||
/// Add production points (from the city's production yield).
|
||||
pub fn add_production(&mut self, production: f64) {
|
||||
self.production_progress += production;
|
||||
}
|
||||
|
||||
// ── Citizens ───────────────────────────────────────────────────
|
||||
|
|
@ -519,8 +519,8 @@ impl City {
|
|||
queueable: Queueable::Item {
|
||||
item_id: def.id.clone(),
|
||||
},
|
||||
hammer_cost: def.hammer_cost,
|
||||
hammers_invested: 0,
|
||||
production_cost: def.production_cost,
|
||||
production_invested: 0,
|
||||
};
|
||||
self.queues
|
||||
.entry(def.building.clone())
|
||||
|
|
@ -529,11 +529,11 @@ impl City {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply `hammers` to one building's queue. Returns completions.
|
||||
/// Apply `production` to one building's queue. Returns completions.
|
||||
pub fn tick_building(
|
||||
&mut self,
|
||||
building: &str,
|
||||
hammers: u32,
|
||||
production: u32,
|
||||
) -> Result<Vec<CompletedEntry>, QueueError> {
|
||||
if !self.has_building(building) {
|
||||
return Err(QueueError::UnknownBuilding {
|
||||
|
|
@ -544,7 +544,7 @@ impl City {
|
|||
.queues
|
||||
.entry(building.to_string())
|
||||
.or_default()
|
||||
.tick(hammers))
|
||||
.tick(production))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -781,7 +781,7 @@ mod tests {
|
|||
let mut registry = ItemRegistry::new();
|
||||
registry.insert(ItemDef {
|
||||
id: "iron_axe".into(),
|
||||
hammer_cost: 30,
|
||||
production_cost: 30,
|
||||
building: "smithy".into(),
|
||||
secondary_building: None,
|
||||
requires_tech: Some("bronze_working".into()),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
//! Validation happens at *enqueue* time, not completion time, and materials
|
||||
//! are consumed up-front (so a queued item is "paid for" and cannot be
|
||||
//! retroactively voided by stockpile drains). This mirrors Civ5's behavior
|
||||
//! and matches the design doc.
|
||||
//! and matches the design doc. Production points are the per-turn currency;
|
||||
//! some JSON data files still use the legacy key `hammer_cost` (accepted via
|
||||
//! serde alias).
|
||||
|
||||
use mc_economy::StockpileError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -20,7 +22,8 @@ use std::collections::HashMap;
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ItemDef {
|
||||
pub id: String,
|
||||
pub hammer_cost: u32,
|
||||
#[serde(alias = "hammer_cost")]
|
||||
pub production_cost: u32,
|
||||
pub building: String,
|
||||
#[serde(default)]
|
||||
pub secondary_building: Option<String>,
|
||||
|
|
@ -73,19 +76,21 @@ pub enum Queueable {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct QueueEntry {
|
||||
pub queueable: Queueable,
|
||||
pub hammer_cost: u32,
|
||||
pub hammers_invested: u32,
|
||||
#[serde(alias = "hammer_cost")]
|
||||
pub production_cost: u32,
|
||||
#[serde(alias = "hammers_invested")]
|
||||
pub production_invested: u32,
|
||||
}
|
||||
|
||||
impl QueueEntry {
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.hammers_invested >= self.hammer_cost
|
||||
self.production_invested >= self.production_cost
|
||||
}
|
||||
pub fn progress(&self) -> f32 {
|
||||
if self.hammer_cost == 0 {
|
||||
if self.production_cost == 0 {
|
||||
1.0
|
||||
} else {
|
||||
(self.hammers_invested as f32 / self.hammer_cost as f32).min(1.0)
|
||||
(self.production_invested as f32 / self.production_cost as f32).min(1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,24 +123,24 @@ impl BuildingQueue {
|
|||
self.entries.push(entry);
|
||||
}
|
||||
|
||||
/// Apply `hammers` to the head entry. Returns any items that completed
|
||||
/// (hammer overflow rolls into the next entry). Completed entries are
|
||||
/// Apply `production` to the head entry. Returns any items that completed
|
||||
/// (production overflow rolls into the next entry). Completed entries are
|
||||
/// removed from the queue in FIFO order.
|
||||
pub fn tick(&mut self, mut hammers: u32) -> Vec<CompletedEntry> {
|
||||
pub fn tick(&mut self, mut production: u32) -> Vec<CompletedEntry> {
|
||||
let mut completed = Vec::new();
|
||||
while hammers > 0 && !self.entries.is_empty() {
|
||||
while production > 0 && !self.entries.is_empty() {
|
||||
let head = &mut self.entries[0];
|
||||
let needed = head.hammer_cost.saturating_sub(head.hammers_invested);
|
||||
let applied = hammers.min(needed);
|
||||
head.hammers_invested += applied;
|
||||
hammers -= applied;
|
||||
let needed = head.production_cost.saturating_sub(head.production_invested);
|
||||
let applied = production.min(needed);
|
||||
head.production_invested += applied;
|
||||
production -= applied;
|
||||
if head.is_complete() {
|
||||
let entry = self.entries.remove(0);
|
||||
completed.push(CompletedEntry {
|
||||
queueable: entry.queueable,
|
||||
});
|
||||
} else {
|
||||
// Head not done — even if we have hammers left we can't be
|
||||
// Head not done — even if we have production left we can't be
|
||||
// mid-stuck; break is unreachable but defensive.
|
||||
break;
|
||||
}
|
||||
|
|
@ -213,7 +218,7 @@ mod tests {
|
|||
fn iron_axe() -> ItemDef {
|
||||
ItemDef {
|
||||
id: "iron_axe".into(),
|
||||
hammer_cost: 30,
|
||||
production_cost: 30,
|
||||
building: "smithy".into(),
|
||||
secondary_building: None,
|
||||
requires_tech: Some("bronze_working".into()),
|
||||
|
|
@ -227,7 +232,7 @@ mod tests {
|
|||
fn dwarven_plate() -> ItemDef {
|
||||
ItemDef {
|
||||
id: "dwarven_plate".into(),
|
||||
hammer_cost: 90,
|
||||
production_cost: 90,
|
||||
building: "forge".into(),
|
||||
secondary_building: Some("tannery".into()),
|
||||
requires_tech: Some("steelworking".into()),
|
||||
|
|
@ -268,7 +273,7 @@ mod tests {
|
|||
assert_eq!(stockpile.available("iron_ore"), 0);
|
||||
assert_eq!(city.queue_for("smithy").unwrap().len(), 1);
|
||||
|
||||
// 30 hammers in one tick = complete.
|
||||
// 30 production in one tick = complete.
|
||||
let done = city.tick_building("smithy", 30).unwrap();
|
||||
assert_eq!(done.len(), 1);
|
||||
assert_eq!(
|
||||
|
|
@ -368,11 +373,11 @@ mod tests {
|
|||
city.enqueue_item("iron_axe", ®istry(), &mut stockpile, &techs(&["bronze_working"]))
|
||||
.unwrap();
|
||||
|
||||
// 10 hammers → not done.
|
||||
// 10 production → not done.
|
||||
let done = city.tick_building("smithy", 10).unwrap();
|
||||
assert!(done.is_empty());
|
||||
assert_eq!(
|
||||
city.queue_for("smithy").unwrap().entries()[0].hammers_invested,
|
||||
city.queue_for("smithy").unwrap().entries()[0].production_invested,
|
||||
10
|
||||
);
|
||||
|
||||
|
|
@ -392,7 +397,7 @@ mod tests {
|
|||
.unwrap();
|
||||
city.enqueue_item("iron_axe", &r, &mut stockpile, &techs(&["bronze_working"]))
|
||||
.unwrap();
|
||||
// Two entries × 30 hammers each = 60 total.
|
||||
// Two entries x 30 production each = 60 total.
|
||||
let done = city.tick_building("smithy", 60).unwrap();
|
||||
assert_eq!(done.len(), 2);
|
||||
}
|
||||
|
|
@ -427,7 +432,7 @@ mod tests {
|
|||
assert_eq!(done_axe.len(), 1);
|
||||
assert!(done_plate.is_empty());
|
||||
assert_eq!(
|
||||
city.queue_for("forge").unwrap().entries()[0].hammers_invested,
|
||||
city.queue_for("forge").unwrap().entries()[0].production_invested,
|
||||
50
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue