feat(engine): 四座自动化设施——喷灌 / 收割机 / 售货机 / 磨坊
为什么:自动化建筑只有水井和喂食器,中期以后每天的操作只剩浇水喂食 的重复劳动。新设施把"替代行动点动作"的思路推到完整流水线: 浇水(井/喷灌)→ 收割机收熟地 → 磨坊把陈粮变饲料 → 喂食器喂牲口 → 售货机趁高价清库存,人不在也能持续产出。 - config.BUILDINGS 注册 4 座:喷灌系统 2800 金(全量浇水)、 自动收割机 900 金 ×3(每天各收 2 块熟地,刻意不给经验——机器收的 不算劳动,给手动玩法留价值)、自动售货机 1200 金(市价 > 基准价才 出手、每天上限 5 份、越贵越先卖)、饲料磨坊 500 金(1 份最便宜的 作物磨 3 份饲料)。 - engine._run_buildings 扩成六段流水线,顺序即依赖:磨坊排在喂食器前, 早上磨的饲料当天就能喂上;收割机遇满仓停摆、作物留地(同喂食器 缺料停摆哲学——自动系统绝不硬来,更绝不自动花钱)。 - webui / build 动作 / catalog 全部由 config.BUILDINGS 驱动,零改动。 - 测试 +6(喷灌全量浇水、收割入库且无经验、满仓停摆留作物、磨坊挑 最便宜、磨坊→喂食器同日链路、售货机阈值与配额)。修复断言时确认 两个既有行为:引擎自带 START_FEED=5;advance_day 末尾会把动物 fed 复位("喂到了"的证据是 pending 累计而非 fed 本身)。 - 门禁:534 项 OK(skip 39)、ruff/pyright 全绿。
This commit is contained in:
1 parent
2676927c1a
commit
cb4c1e7800
7 files changed
+332
-10
No files matched your search
@@ -289,6 +289,10 @@ MAX_GRANARY = 400 # 仓容硬上限
|
||||
# max_level : 最多能造几座
|
||||
# effect : 一句话说明(原样出现在 catalog 与界面上)
|
||||
|
||||
HARVESTER_PLOTS = 2 # 每台自动收割机每天收获的地块数
|
||||
VENDOR_QUOTA = 5 # 自动售货机每天最多卖出的份数
|
||||
MILL_OUTPUT = 3 # 饲料磨坊每份作物磨出的饲料量
|
||||
|
||||
BUILDINGS: dict[str, dict[str, Any]] = {
|
||||
"granary": {
|
||||
"name": "谷仓",
|
||||
@@ -318,6 +322,34 @@ BUILDINGS: dict[str, dict[str, Any]] = {
|
||||
"max_level": 1,
|
||||
"effect": "作物不再被暴风雨损毁,水分蒸发减半",
|
||||
},
|
||||
"sprinkler": {
|
||||
"name": "喷灌系统",
|
||||
"cost": 2800,
|
||||
"level_req": 4,
|
||||
"max_level": 1,
|
||||
"effect": "每天自动给所有缺水的地块浇满",
|
||||
},
|
||||
"harvester": {
|
||||
"name": "自动收割机",
|
||||
"cost": 900,
|
||||
"level_req": 3,
|
||||
"max_level": 3,
|
||||
"effect": f"每天自动收获 {HARVESTER_PLOTS} 块成熟作物入谷仓(不给经验)",
|
||||
},
|
||||
"vendor": {
|
||||
"name": "自动售货机",
|
||||
"cost": 1200,
|
||||
"level_req": 4,
|
||||
"max_level": 1,
|
||||
"effect": f"每天自动卖出市价高于基准价的库存(上限 {VENDOR_QUOTA} 份)",
|
||||
},
|
||||
"mill": {
|
||||
"name": "饲料磨坊",
|
||||
"cost": 500,
|
||||
"level_req": 2,
|
||||
"max_level": 1,
|
||||
"effect": f"每天把 1 份库存作物磨成 {MILL_OUTPUT} 份饲料(挑最便宜的)",
|
||||
},
|
||||
}
|
||||
|
||||
BUILD_PRICE_GROWTH = 1.6 # 同类建筑第 n 座的价格系数
|
||||
|
||||
+106
-5
@@ -1066,13 +1066,114 @@ class FarmEngine:
|
||||
return evaporate
|
||||
|
||||
def _run_buildings(self, state: GameState) -> list[str]:
|
||||
"""结算建筑的每日自动动作:水井浇水、喂食器喂食。
|
||||
"""结算建筑的每日自动动作。
|
||||
|
||||
两件事都只替代"玩家本来要花行动点做的事",且都**不替玩家花钱**:
|
||||
喂食器只在库存饲料够时动作,缺了就停摆并记一条日志,而不是像手动
|
||||
喂食那样以应急价临时采购——自动系统悄悄花光金币是最容易被骂的设计。
|
||||
顺序是一条流水线:浇水(水井/喷灌)保住作物 → 收割机把熟了的收进
|
||||
谷仓 → 磨坊把陈粮磨成饲料 → 喂食器喂牲口 → 售货机趁高价清库存。
|
||||
共同的设计底线:**只替代玩家本来要花行动点做的事,绝不自动花钱**
|
||||
——喂食器缺饲料停摆、收割机遇满仓停摆,都宁可停也不硬来。
|
||||
"""
|
||||
return self._wells(state) + self._feeders(state)
|
||||
return (
|
||||
self._wells(state)
|
||||
+ self._sprinklers(state)
|
||||
+ self._harvesters(state)
|
||||
+ self._mill(state)
|
||||
+ self._feeders(state)
|
||||
+ self._vendor(state)
|
||||
)
|
||||
|
||||
def _sprinklers(self, state: GameState) -> list[str]:
|
||||
"""喷灌系统:水井的全量版,一口气浇完所有干透的地(限 1 座)。"""
|
||||
if state.building_count("sprinkler") <= 0:
|
||||
return []
|
||||
events: list[str] = []
|
||||
for plot in state.plots:
|
||||
if plot.crop is None or plot.moisture > 0:
|
||||
continue
|
||||
plot.moisture = config.WATER_CAPACITY
|
||||
events.append(f"喷灌系统给 {plot.index + 1} 号地浇了水")
|
||||
return events
|
||||
|
||||
def _harvesters(self, state: GameState) -> list[str]:
|
||||
"""自动收割机:每天替玩家收熟地,入库逻辑与手动收获完全一致。
|
||||
|
||||
刻意**不给经验**——机器收的不算你的劳动,手动收获才涨等级,
|
||||
这是自动化换手感时留的手动价值。谷仓满了就停摆(同喂食器哲学),
|
||||
作物留在地里,卖了就能恢复。
|
||||
"""
|
||||
quota = state.building_count("harvester") * config.HARVESTER_PLOTS
|
||||
if quota <= 0:
|
||||
return []
|
||||
events: list[str] = []
|
||||
for plot in state.plots:
|
||||
if quota <= 0:
|
||||
break
|
||||
if plot.crop is None or not plot.is_ripe:
|
||||
continue
|
||||
crop_key = plot.crop
|
||||
quantity = self._roll_yield()
|
||||
try:
|
||||
self._store(crop_key, quantity)
|
||||
except GameError:
|
||||
events.append("谷仓满了,自动收割机停摆")
|
||||
break
|
||||
plot.crop = None
|
||||
plot.growth = 0.0
|
||||
plot.moisture = 0
|
||||
state.total_harvested += quantity
|
||||
quota -= 1
|
||||
events.append(
|
||||
f"自动收割机把 {plot.index + 1} 号地的{config.CROPS[crop_key]['name']}收进了谷仓"
|
||||
)
|
||||
return events
|
||||
|
||||
def _mill(self, state: GameState) -> list[str]:
|
||||
"""饲料磨坊:把 1 份库存作物磨成饲料,挑**最便宜的**下手。
|
||||
|
||||
排在喂食器前面:早上磨好,喂食器当天就能吃上。只磨谷仓里的存货,
|
||||
绝不自动买粮——"自动系统悄悄花光金币"是本项目的红线。
|
||||
"""
|
||||
if state.building_count("mill") <= 0 or not state.inventory:
|
||||
return []
|
||||
key = min(state.inventory, key=config.base_price)
|
||||
state.inventory[key] -= 1
|
||||
if state.inventory[key] <= 0:
|
||||
del state.inventory[key]
|
||||
state.feed += config.MILL_OUTPUT
|
||||
return [f"饲料磨坊把 1 份{config.goods_name(key)}磨成了 {config.MILL_OUTPUT} 份饲料"]
|
||||
|
||||
def _vendor(self, state: GameState) -> list[str]:
|
||||
"""自动售货机:市价高于基准价的库存自动出手,越贵越先卖。
|
||||
|
||||
卖出是赚钱不是花钱,所以它是唯一"主动碰金币"的自动化——但每天
|
||||
有份数上限,且基准价以下的存货绝不碰,留着等高价。
|
||||
"""
|
||||
quota = state.building_count("vendor") * config.VENDOR_QUOTA
|
||||
if quota <= 0 or not state.inventory:
|
||||
return []
|
||||
offers = []
|
||||
for key in sorted(state.inventory):
|
||||
base = config.base_price(key)
|
||||
if base <= 0:
|
||||
continue
|
||||
ratio = self.market_price(key) / base
|
||||
if ratio > 1.0:
|
||||
offers.append((ratio, key))
|
||||
offers.sort(reverse=True)
|
||||
events: list[str] = []
|
||||
for _, key in offers:
|
||||
if quota <= 0:
|
||||
break
|
||||
qty = min(state.inventory[key], quota)
|
||||
earned = self._earn(self.market_price(key) * qty, config.goods_name(key))
|
||||
left = state.inventory[key] - qty
|
||||
if left > 0:
|
||||
state.inventory[key] = left
|
||||
else:
|
||||
del state.inventory[key]
|
||||
quota -= qty
|
||||
events.append(f"自动售货机卖出 {qty} 份{config.goods_name(key)},+{earned} 金币")
|
||||
return events
|
||||
|
||||
def _wells(self, state: GameState) -> list[str]:
|
||||
quota = state.building_count("well")
|
||||
|
||||
Reference in new issue
Block a user