init
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# Godot 4.x
|
||||||
|
.godot/
|
||||||
|
.idea/
|
||||||
|
sdk/
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
#ai
|
||||||
|
.omo/
|
||||||
|
.opencode/
|
||||||
|
|
||||||
|
gen/
|
||||||
|
obj/
|
||||||
|
bin/
|
||||||
|
|
||||||
|
*.tres~
|
||||||
|
*.tscn~
|
||||||
|
|
||||||
|
# GDScript LSP
|
||||||
|
.godot/
|
||||||
|
|
||||||
|
# Export
|
||||||
|
*.exe
|
||||||
|
*.dmg
|
||||||
|
*.apk
|
||||||
|
*.aab
|
||||||
|
*.pck
|
||||||
|
*.zip
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
*.import
|
||||||
|
*.godot.uid
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Items": [
|
||||||
|
${recipe/wolf_meat_to_ration}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Id": "recipe/wolf_meat_to_ration",
|
||||||
|
"Name": "items/recipe/wolf_meat_to_ration/name",
|
||||||
|
"Description": "items/recipe/wolf_meat_to_ration/desc",
|
||||||
|
"Cost": 1,
|
||||||
|
"Sprite": "recipe/cooking_recipe",
|
||||||
|
"ItemType": "Recipe,Used",
|
||||||
|
"ItemAction": "action/recipe/wolf_meat_to_ration"
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
mod_cooking_name = Cooking
|
||||||
|
mod_cooking_desc = Cooking mechanic — full autonomous mod
|
||||||
|
|
||||||
|
ui/cook/cook = Cook
|
||||||
|
ui/cook/close = Close
|
||||||
|
ui/cook/title = Food preparation
|
||||||
|
ui/cook/recipe_time = {0} minutes
|
||||||
|
ui/cook/recipe_out = Cooking result: {0}
|
||||||
|
ui/diner/rationcount = Rations: {0}
|
||||||
|
ui/diner/rationday = Daily consumption: {0}
|
||||||
|
ui/diner/rationmax = Supplies will last {0} days
|
||||||
|
items/food/ration/name = Ration
|
||||||
|
items/food/ration/desc = A daily ration unit for one person
|
||||||
|
items/food/wolf_meat/name = Wolf cutlet
|
||||||
|
items/food/wolf_meat/desc = Raw wolf meat
|
||||||
|
items/resource/branches/name = Branches
|
||||||
|
items/resource/branches/desc = Fuel for a campfire
|
||||||
|
recipe/wolf_meat_to_ration/name = Ration from wolf meat
|
||||||
|
recipe/wolf_meat_to_ration/desc = A recipe describing how to properly cook wolf meat to make it safe to eat
|
||||||
|
mod_cooking/rations_per_character = Rations per person per day
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
mod_cooking_name = Готовка
|
||||||
|
mod_cooking_desc = Механика готовки — полностью автономный мод
|
||||||
|
|
||||||
|
ui/cook/cook = Готовить
|
||||||
|
ui/cook/close = Выйти
|
||||||
|
ui/cook/title = Приготовление еды
|
||||||
|
ui/cook/recipe_time = {0} минут
|
||||||
|
ui/cook/recipe_out = Результат приготовления: {0}
|
||||||
|
ui/diner/rationcount = Пайков: {0}
|
||||||
|
ui/diner/rationday = Потребление в сутки: {0}
|
||||||
|
ui/diner/rationmax = Запасов хватит на {0} дней
|
||||||
|
items/food/ration/name = Рацион
|
||||||
|
items/food/ration/desc = Единица суточного рациона для одного человека
|
||||||
|
items/food/wolf_meat/name = Волчья вырезка
|
||||||
|
items/food/wolf_meat/desc = Сырое мясо волка
|
||||||
|
items/resource/branches/name = Ветки
|
||||||
|
items/resource/branches/desc = Топливо для костра
|
||||||
|
recipe/wolf_meat_to_ration/name = Рацион из волчьего мяса
|
||||||
|
recipe/wolf_meat_to_ration/desc = Рецепт описывает как правильно приготовить волчатину, чтобы безопасно употребить её в пищу
|
||||||
|
mod_cooking/rations_per_character = Рационов на персонажа в сутки
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
rooms/kitchen/prompt = A room equipped as a kitchen
|
||||||
|
rooms/kitchen/opt_exit = Leave
|
||||||
|
rooms/kitchen/opt_cook = Cook
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"id": "cooking",
|
||||||
|
"name": "Cooking",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "ct",
|
||||||
|
"description": "Cooking mechanic - full autonomous mod",
|
||||||
|
"sources": [
|
||||||
|
"src/*.cs"
|
||||||
|
],
|
||||||
|
"depends": [ "core>=1", "interface>=1" ],
|
||||||
|
"subscriptions": [
|
||||||
|
{
|
||||||
|
"scene": "home_kitchen",
|
||||||
|
"template": "default",
|
||||||
|
"code": "cooking_panel_init",
|
||||||
|
"trigger": "on_enter",
|
||||||
|
"priority": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scene": "home_kitchen",
|
||||||
|
"template": "default",
|
||||||
|
"code": "dinner_tools_show",
|
||||||
|
"trigger": "on_enter",
|
||||||
|
"priority": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scene": "home_kitchen",
|
||||||
|
"template": "default",
|
||||||
|
"code": "dinner_tools_hide",
|
||||||
|
"trigger": "on_exit",
|
||||||
|
"priority": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"Id": "recipe/wolf_meat_to_ration",
|
||||||
|
"Name": "recipe/wolf_meat_to_ration/name",
|
||||||
|
"Description": "recipe/wolf_meat_to_ration/desc",
|
||||||
|
"Type": "Cooking",
|
||||||
|
"Time": 15,
|
||||||
|
"Input": [
|
||||||
|
{
|
||||||
|
"Id": "food/wolf_meat",
|
||||||
|
"Count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "resource/branches",
|
||||||
|
"Count": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Output": [
|
||||||
|
{
|
||||||
|
"Id": "food/ration",
|
||||||
|
"Count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
scene: home_kitchen
|
||||||
|
priority: 5
|
||||||
|
---
|
||||||
|
light_set [{"x":0.43052286,"y":0.5760054,"radius":500,"influence":1,"color":"#ffff00"},{"x":0.103702,"y":0.26946074,"radius":500,"influence":1,"color":"#ffff00"}]
|
||||||
|
select "Комната оборудованная под кухню" key=rooms/kitchen/prompt
|
||||||
|
option "Выйти" key=rooms/kitchen/opt_exit -> :exit
|
||||||
|
option "Готовить" key=rooms/kitchen/opt_cook -> :cook
|
||||||
|
|
||||||
|
:exit
|
||||||
|
switch_scene home_vestibule
|
||||||
|
end
|
||||||
|
|
||||||
|
:cook
|
||||||
|
action toggle_cooking_workbench
|
||||||
|
end
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/ui/workbench/WorkbenchPanel.cs" id="1_panel"]
|
||||||
|
[ext_resource type="Texture2D" path="res://backgrounds/cook_table.png" id="2_bg"]
|
||||||
|
|
||||||
|
[node name="CookingWorkbenchPanel" type="Control" script=ExtResource("1_panel")]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
[node name="BgImage" type="TextureRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
offset_right = 0.0
|
||||||
|
mouse_filter = 2
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 4
|
||||||
|
texture = ExtResource("2_bg")
|
||||||
|
[node name="HBox" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="LeftPanel" type="VBoxContainer" parent="HBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
custom_minimum_size = Vector2(400, 0)
|
||||||
|
|
||||||
|
[node name="LeftTitle" type="Label" parent="HBox/LeftPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Scroll" type="ScrollContainer" parent="HBox/LeftPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
horizontal_scroll_mode = 3
|
||||||
|
|
||||||
|
[node name="Content" type="VBoxContainer" parent="HBox/LeftPanel/Scroll"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="RightPanel" type="VBoxContainer" parent="HBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
custom_minimum_size = Vector2(300, 0)
|
||||||
|
|
||||||
|
[node name="TxtDescription" type="RichTextLabel" parent="HBox/RightPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
bbcode_enabled = true
|
||||||
|
scroll_active = true
|
||||||
|
custom_minimum_size = Vector2(0, 100)
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="BtnCook" type="Button" parent="HBox/RightPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="BtnClose" type="Button" parent="HBox/RightPanel"]
|
||||||
|
layout_mode = 2
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/ui/RationStatisticsPanel.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="RationStatisticsPanel" type="Control" script=ExtResource("1_script")]
|
||||||
|
|
||||||
|
[node name="RationMargin" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_left = 50
|
||||||
|
theme_override_constants/margin_top = 100
|
||||||
|
theme_override_constants/margin_bottom = 100
|
||||||
|
|
||||||
|
[node name="RationLabel" type="RichTextLabel" parent="RationMargin"]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/ui/workbench/RecipeIconItemBehaviour.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="RecipeIconItem" type="Control" script=ExtResource("1_script")]
|
||||||
|
|
||||||
|
[node name="Img" type="TextureRect" parent="."]
|
||||||
|
custom_minimum_size = Vector2(32, 32)
|
||||||
|
layout_mode = 0
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
|
||||||
|
[node name="Txt" type="Label" parent="."]
|
||||||
|
layout_mode = 0
|
||||||
|
position = Vector2(24, 20)
|
||||||
|
horizontal_alignment = 2
|
||||||
|
vertical_alignment = 2
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="src/ui/workbench/RecipeItemBehaviour.cs" id="1_script"]
|
||||||
|
|
||||||
|
[node name="RecipeItem" type="Control" script=ExtResource("1_script")]
|
||||||
|
custom_minimum_size = Vector2(0, 30)
|
||||||
|
|
||||||
|
[node name="Background" type="Control" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="HBox" type="HBoxContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="RecipeContent" type="Control" parent="HBox"]
|
||||||
|
custom_minimum_size = Vector2(100, 30)
|
||||||
|
|
||||||
|
[node name="RecipeContentHbox" type="HBoxContainer" parent="HBox/RecipeContent"]
|
||||||
|
|
||||||
|
[node name="TxtName" type="Label" parent="HBox"]
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 0, 1)
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
|
||||||
|
[node name="TxtTime" type="Label" parent="HBox"]
|
||||||
|
custom_minimum_size = Vector2(40, 30)
|
||||||
|
theme_override_colors/font_color = Color(1, 0, 0, 1)
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
Binary file not shown.
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Cthangover.Core.Mods;
|
||||||
|
using Cthangover.Core.Settings;
|
||||||
|
|
||||||
|
namespace Cthangover.Cooking
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Exposes a user-configurable setting for the number of rations
|
||||||
|
/// consumed per character per in-game day. Registered automatically
|
||||||
|
/// via <see cref="ModSettingsRegistry"/> when the cooking mod
|
||||||
|
/// assembly loads.
|
||||||
|
///
|
||||||
|
/// The static <see cref="RationsPerCharacter"/> property is the
|
||||||
|
/// canonical source of truth shared with <c>RationStatisticsPanel</c>
|
||||||
|
/// and any future consumption logic. It is updated by the core on
|
||||||
|
/// boot (from persisted JSON) and on every UI change.
|
||||||
|
/// </summary>
|
||||||
|
public class CookingSettings : IModSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Current value for rations consumed per character per day.
|
||||||
|
/// Default is 1. Updated by <see cref="ReadValues"/> on boot
|
||||||
|
/// and whenever the player changes the setting in the menu.
|
||||||
|
/// </summary>
|
||||||
|
public static int RationsPerCharacter { get; private set; } = 1;
|
||||||
|
|
||||||
|
public IReadOnlyList<ModSettingDefinition> GetDefinitions()
|
||||||
|
{
|
||||||
|
return new List<ModSettingDefinition>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Key = "rations_per_character",
|
||||||
|
Name = "mod_cooking/rations_per_character",
|
||||||
|
Type = SettingType.Slider,
|
||||||
|
DefaultValue = "1",
|
||||||
|
Min = 1f,
|
||||||
|
Max = 10f,
|
||||||
|
Step = 1f,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteValues(DataBlob blob)
|
||||||
|
{
|
||||||
|
blob.SetInt("rations_per_character", RationsPerCharacter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadValues(DataBlob blob)
|
||||||
|
{
|
||||||
|
RationsPerCharacter = blob.GetInt("rations_per_character", 1);
|
||||||
|
if (RationsPerCharacter < 1)
|
||||||
|
RationsPerCharacter = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using Cthangover.Core.Relationship;
|
||||||
|
using Cthangover.Core.Settings;
|
||||||
|
|
||||||
|
namespace Cthangover.Core.Relationship
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks nutrition and starvation for party recruits.
|
||||||
|
/// Works by recording the tick when a recruit last ate (<c>FullnessTime</c>),
|
||||||
|
/// then each tick computes the elapsed time as a percentage of a 24-hour window.
|
||||||
|
/// Once the fullness window expires (100%+), the recruit loses 1 HP per tick
|
||||||
|
/// until fed again via a ration or meal action.
|
||||||
|
/// Registered with the <see cref="Recruit"/> behaviour pipeline and called
|
||||||
|
/// by the time system on each global tick.
|
||||||
|
/// </summary>
|
||||||
|
public class NutritionBehaviour : IRecruitBehaviour
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique behaviour identifier used to look up and configure this
|
||||||
|
/// behaviour from <see cref="Recruit"/>'s behaviour collection.
|
||||||
|
/// </summary>
|
||||||
|
public string Id => "nutrition";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the behaviour is first attached to a recruit.
|
||||||
|
/// Seeds <c>FullnessTime</c> with the current global tick so that
|
||||||
|
/// starve-damage begins counting from this point.
|
||||||
|
/// </summary>
|
||||||
|
public void ConfigureRecruit(Recruit recruit, RuntimeData runtime)
|
||||||
|
{
|
||||||
|
recruit.Properties.SetLong("FullnessTime", runtime.Time.Tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates whether the recruit has gone beyond the fullness window
|
||||||
|
/// (24 in-game hours, represented as <c>24 * 4 * 60</c> ticks) since
|
||||||
|
/// <c>FullnessTime</c>. If the window is exceeded, reduces the
|
||||||
|
/// recruit's <c>PROP_HEALTH</c> by 1 per tick until they are fed.
|
||||||
|
/// Tick-driven starvation allows gradual death when rations run out.
|
||||||
|
/// </summary>
|
||||||
|
public void OnTick(Recruit recruit, RuntimeData runtime, long currentTick)
|
||||||
|
{
|
||||||
|
var fullnessTime = recruit.Properties.GetLong("FullnessTime");
|
||||||
|
if (fullnessTime == 0)
|
||||||
|
return;
|
||||||
|
var windowSize = 24 * 4 * 60L;
|
||||||
|
var deltaTime = currentTick - fullnessTime;
|
||||||
|
var fullness = (float)deltaTime / windowSize * 100f;
|
||||||
|
if (fullness > 100)
|
||||||
|
{
|
||||||
|
var hp = recruit.Properties.GetInt(Recruit.PROP_HEALTH);
|
||||||
|
if (hp > 0)
|
||||||
|
recruit.Properties.SetInt(Recruit.PROP_HEALTH, hp - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleanup hook invoked when the behaviour is detached from a recruit.
|
||||||
|
/// No persistent state requires teardown; exists for interface contract.
|
||||||
|
/// </summary>
|
||||||
|
public void OnRemove(Recruit recruit, RuntimeData runtime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Cthangover.Core.Settings;
|
||||||
|
|
||||||
|
namespace Cthangover.Core.Items.Actions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the "Wolf Meat to Ration" recipe-unlock action.
|
||||||
|
/// When a player uses raw wolf meat, this action checks whether the
|
||||||
|
/// <c>recipe/wolf_meat_to_ration</c> flag is already present in
|
||||||
|
/// <see cref="RecipeData"/>; if not, it adds it, permanently unlocking
|
||||||
|
/// the recipe in the <see cref="Mods.Cooking.Workbench.WorkbenchPanel"/>.
|
||||||
|
/// Returns <c>false</c> when already unlocked (no-op) and <c>true</c>
|
||||||
|
/// on first unlock.
|
||||||
|
/// </summary>
|
||||||
|
public class WolfMeatToRation : IItemAction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Globally unique action identifier, matched by the item system
|
||||||
|
/// to trigger this action when an item with this ID is used.
|
||||||
|
/// </summary>
|
||||||
|
public string ID => "action/recipe/wolf_meat_to_ration";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Idempotently unlocks the wolf-meat-to-ration recipe.
|
||||||
|
/// Adds <c>recipe/wolf_meat_to_ration</c> to <see cref="RecipeData"/>
|
||||||
|
/// so that the cooking workbench can list it.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> if the recipe was newly unlocked; <c>false</c> if already known.</returns>
|
||||||
|
public bool UseAction(IItem item)
|
||||||
|
{
|
||||||
|
var data = GameData.Instance.Runtime.RecipeData;
|
||||||
|
if (data.Has("recipe/wolf_meat_to_ration"))
|
||||||
|
return false;
|
||||||
|
data.Add("recipe/wolf_meat_to_ration");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using Cthangover.Cooking;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Cthangover.Core.Settings;
|
||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mods.Cooking.Rations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays live ration statistics on the dinner/ration UI screen.
|
||||||
|
/// Shows the player the current ration count, how many rations are
|
||||||
|
/// consumed daily (one per character), and the maximum number of days
|
||||||
|
/// the party can survive on available rations. Listens to
|
||||||
|
/// <see cref="Core.Items.InventoryBag.Change"/> events to refresh
|
||||||
|
/// automatically whenever the inventory is modified.
|
||||||
|
/// </summary>
|
||||||
|
public class RationStatisticsPanel : Widget
|
||||||
|
{
|
||||||
|
private RichTextLabel label;
|
||||||
|
private Action<string, int> _onInventoryChange;
|
||||||
|
|
||||||
|
protected override void OnceConstruct()
|
||||||
|
{
|
||||||
|
label = GetNode<RichTextLabel>("RationMargin/RationLabel");
|
||||||
|
|
||||||
|
_onInventoryChange = (_, _) => UpdateInfo();
|
||||||
|
GameData.Instance.Runtime.Inventory.Change += _onInventoryChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ShowConstruct()
|
||||||
|
{
|
||||||
|
UpdateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnceDestruct()
|
||||||
|
{
|
||||||
|
GameData.Instance.Runtime.Inventory.Change -= _onInventoryChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recomputes and refreshes the ration-statistics text.
|
||||||
|
/// Reads current ration inventory via <see cref="Core.Items.InventoryBag.CheckCount"/>
|
||||||
|
/// for <c>"food/ration"</c> and the total character count from
|
||||||
|
/// <see cref="CharacterData"/>. Formats three localized lines:
|
||||||
|
/// total rations, daily consumption, and remaining days
|
||||||
|
/// (rations divided by character count, clamped to 1 minimum).
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateInfo()
|
||||||
|
{
|
||||||
|
if (label == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rationCount = GameData.Instance.Runtime.Inventory.CheckCount("food/ration");
|
||||||
|
var characterCount = GameData.Instance.Runtime.CharacterData.Characters.Count;
|
||||||
|
var perChar = CookingSettings.RationsPerCharacter;
|
||||||
|
var dailyConsumption = characterCount * perChar;
|
||||||
|
|
||||||
|
label.Text = string.Format(TranslationServer.Translate("ui/diner/rationcount"), rationCount) +
|
||||||
|
"\n" + string.Format(TranslationServer.Translate("ui/diner/rationday"), dailyConsumption) +
|
||||||
|
"\n" + string.Format(TranslationServer.Translate("ui/diner/rationmax"), rationCount / Mathf.Max(1, dailyConsumption));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using Cthangover.Core.Items;
|
||||||
|
using Cthangover.Core.Settings;
|
||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.Utils;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mods.Cooking.Workbench
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Renders a single ingredient row inside a <see cref="RecipeItemBehaviour"/>
|
||||||
|
/// on the cooking workbench. Displays the ingredient's item icon and the
|
||||||
|
/// required count. When <see cref="CheckAndUpdate"/> is called, compares
|
||||||
|
/// inventory count against the recipe requirement and tints the icon and
|
||||||
|
/// count text grey if the player lacks enough items.
|
||||||
|
/// </summary>
|
||||||
|
public class RecipeIconItemBehaviour : Widget
|
||||||
|
{
|
||||||
|
private static readonly Color NormalColor = Colors.White;
|
||||||
|
private static readonly Color NotItemsColor = Colors.Gray;
|
||||||
|
|
||||||
|
private TextureRect img;
|
||||||
|
private Label txt;
|
||||||
|
private IIngredient ingredient;
|
||||||
|
|
||||||
|
protected override void OnceConstruct()
|
||||||
|
{
|
||||||
|
img = GetNode<TextureRect>("Img");
|
||||||
|
txt = GetNode<Label>("Txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds this icon widget to a recipe ingredient definition.
|
||||||
|
/// Sets the icon texture from <c>ingredient.Item.Sprite</c> (logs an error
|
||||||
|
/// if the item reference is null) and displays the required count as text.
|
||||||
|
/// The <paramref name="hasItems"/> flag is currently unused but reserved
|
||||||
|
/// for future visual pre-initialization.
|
||||||
|
/// </summary>
|
||||||
|
public void Init(IIngredient ingredient, bool hasItems)
|
||||||
|
{
|
||||||
|
EnsureConstructed();
|
||||||
|
this.ingredient = ingredient;
|
||||||
|
if (ingredient.Item != null)
|
||||||
|
{
|
||||||
|
img.Texture = ingredient.Item.Sprite;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameLogger.Log("MOD_TEST", $"RecipeIconItemBehaviour::Init ingredient.Item is NULL", LogLevel.Error);
|
||||||
|
}
|
||||||
|
txt.Text = ingredient.Count.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates whether the player's inventory contains enough of the
|
||||||
|
/// required ingredient. Tints the icon and count text to
|
||||||
|
/// <c>NormalColor</c> (white) when satisfied or <c>NotItemsColor</c>
|
||||||
|
/// (grey) when insufficient. Called by the parent
|
||||||
|
/// <see cref="RecipeItemBehaviour"/> during state refresh.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><c>true</c> if inventory meets or exceeds the required count.</returns>
|
||||||
|
public bool CheckAndUpdate()
|
||||||
|
{
|
||||||
|
var result = GameData.Instance.Runtime.Inventory.CheckCount(ingredient.Item) >= ingredient.Count;
|
||||||
|
txt.AddThemeColorOverride("font_color", result ? NormalColor : NotItemsColor);
|
||||||
|
img.Modulate = result ? Colors.White : NotItemsColor;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Cthangover.Core.Items;
|
||||||
|
using Cthangover.Core.Scenes;
|
||||||
|
using Cthangover.Core.Settings;
|
||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mods.Cooking.Workbench
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single recipe entry in the <see cref="WorkbenchPanel"/> list.
|
||||||
|
/// Displays the recipe name, preparation time, and a horizontal row of
|
||||||
|
/// ingredient icons. Supports click-to-select with visual highlight
|
||||||
|
/// (tracked statically so only one recipe is selected at a time).
|
||||||
|
/// Fires <see cref="RecipeClicked"/> to notify the parent panel of
|
||||||
|
/// selection changes. Ingredient availability is checked via child
|
||||||
|
/// <see cref="RecipeIconItemBehaviour"/> instances, greying out the
|
||||||
|
/// entire row when any ingredient is missing.
|
||||||
|
/// </summary>
|
||||||
|
public class RecipeItemBehaviour : Widget
|
||||||
|
{
|
||||||
|
private Control background;
|
||||||
|
private Control recipeContent;
|
||||||
|
private Label txtName;
|
||||||
|
private Label txtTime;
|
||||||
|
private HBoxContainer recipeContentHbox;
|
||||||
|
|
||||||
|
private static readonly Color normalColor = new(1f, 1f, 1f, 1f);
|
||||||
|
private static readonly Color selectedColor = new(0.8f, 0.8f, 0.8f, 1f);
|
||||||
|
private static readonly Color disableColor = new(0.5f, 0.5f, 0.5f, 1f);
|
||||||
|
|
||||||
|
private IRecipe recipe;
|
||||||
|
private Node panel;
|
||||||
|
|
||||||
|
private static RecipeItemBehaviour selected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The recipe currently selected across all <see cref="RecipeItemBehaviour"/>
|
||||||
|
/// instances. Only one recipe can be selected at a time because the
|
||||||
|
/// selection is tracked via a static backing field. Returns <c>null</c>
|
||||||
|
/// when no recipe is selected.
|
||||||
|
/// </summary>
|
||||||
|
public static IRecipe SelectedRecipe => selected?.recipe;
|
||||||
|
private readonly List<Node> items = new();
|
||||||
|
private readonly List<RecipeIconItemBehaviour> ingredients = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when this recipe entry is clicked, passing the recipe data
|
||||||
|
/// and this widget so that <see cref="WorkbenchPanel"/> can update
|
||||||
|
/// the description and output preview panel.
|
||||||
|
/// </summary>
|
||||||
|
public event System.Action<IRecipe, RecipeItemBehaviour> RecipeClicked;
|
||||||
|
|
||||||
|
protected override void OnceConstruct()
|
||||||
|
{
|
||||||
|
background = GetNode<Control>("Background");
|
||||||
|
recipeContentHbox = GetNode<HBoxContainer>("HBox/RecipeContent/RecipeContentHbox");
|
||||||
|
recipeContent = recipeContentHbox.GetParent() as Control;
|
||||||
|
txtName = GetNode<Label>("HBox/TxtName");
|
||||||
|
txtTime = GetNode<Label>("HBox/TxtTime");
|
||||||
|
|
||||||
|
GuiInput += OnGuiInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnceDestruct()
|
||||||
|
{
|
||||||
|
GuiInput -= OnGuiInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGuiInput(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event is InputEventMouseButton mouseButton && mouseButton.Pressed && mouseButton.ButtonIndex == MouseButton.Left)
|
||||||
|
{
|
||||||
|
OnClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the visual availability state of this recipe.
|
||||||
|
/// Iterates all child <see cref="RecipeIconItemBehaviour"/> widgets,
|
||||||
|
/// calling <c>CheckAndUpdate</c> on each. If any ingredient is missing,
|
||||||
|
/// tints the name and time labels with <c>disableColor</c> (greyed out);
|
||||||
|
/// otherwise restores the default yellow/red colour scheme.
|
||||||
|
/// Call after inventory changes or cooking operations.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateState()
|
||||||
|
{
|
||||||
|
var hasItems = true;
|
||||||
|
|
||||||
|
foreach (var ingredient in ingredients)
|
||||||
|
if (!ingredient.CheckAndUpdate())
|
||||||
|
hasItems = false;
|
||||||
|
|
||||||
|
if (!hasItems)
|
||||||
|
{
|
||||||
|
txtName.AddThemeColorOverride("font_color", disableColor);
|
||||||
|
txtTime.AddThemeColorOverride("font_color", disableColor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
txtName.AddThemeColorOverride("font_color", Colors.Yellow);
|
||||||
|
txtTime.AddThemeColorOverride("font_color", Colors.Red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises the recipe row with recipe data and the parent panel
|
||||||
|
/// reference. Spawns <see cref="RecipeIconItemBehaviour"/> children
|
||||||
|
/// for each ingredient, with plus-sign separators between them.
|
||||||
|
/// Sets the translated recipe name and time, then calls
|
||||||
|
/// <see cref="UpdateState"/> to set initial colour state.
|
||||||
|
/// </summary>
|
||||||
|
public void Init(IRecipe recipe, Node panel)
|
||||||
|
{
|
||||||
|
EnsureConstructed();
|
||||||
|
this.recipe = recipe;
|
||||||
|
this.panel = panel;
|
||||||
|
|
||||||
|
for (int i = 0; i < recipe.Input.Count; i++)
|
||||||
|
{
|
||||||
|
var ingredient = recipe.Input[i];
|
||||||
|
AddIngredient(ingredient, i != recipe.Input.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
txtName.Text = TranslationServer.Translate(recipe.Name);
|
||||||
|
txtTime.Text = string.Format(TranslationServer.Translate("ui/cook/recipe_time"), recipe.Time);
|
||||||
|
|
||||||
|
UpdateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddIngredient(IIngredient ingredient, bool addPlus)
|
||||||
|
{
|
||||||
|
var inventory = GameData.Instance.Runtime.Inventory;
|
||||||
|
|
||||||
|
var item = (RecipeIconItemBehaviour)TscnScenes.LoadAndBuild("scenes/recipe_icon_item.tscn");
|
||||||
|
recipeContentHbox.AddChild(item);
|
||||||
|
var localHasItems = inventory.CheckCount(ingredient.Item) < ingredient.Count;
|
||||||
|
item.Init(ingredient, localHasItems);
|
||||||
|
items.Add(item);
|
||||||
|
ingredients.Add(item);
|
||||||
|
|
||||||
|
if (addPlus)
|
||||||
|
{
|
||||||
|
var plus = new Label();
|
||||||
|
plus.Text = "+";
|
||||||
|
plus.AddThemeFontSizeOverride("font_size", 12);
|
||||||
|
plus.VerticalAlignment = VerticalAlignment.Center;
|
||||||
|
recipeContentHbox.AddChild(plus);
|
||||||
|
items.Add(plus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tears down this recipe row by queue-freeing all child nodes
|
||||||
|
/// (ingredient icons, plus labels, and this widget itself),
|
||||||
|
/// then clears the internal ingredient/item lists.
|
||||||
|
/// Called by <see cref="WorkbenchPanel"/> when the panel is hidden.
|
||||||
|
/// </summary>
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
item.QueueFree();
|
||||||
|
QueueFree();
|
||||||
|
ingredients.Clear();
|
||||||
|
items.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles left-click selection of this recipe entry.
|
||||||
|
/// Deselects the previously-selected entry (if any) by restoring its
|
||||||
|
/// background to normal colour, highlights the background of this
|
||||||
|
/// entry, updates the static <c>selected</c> tracker, and invokes
|
||||||
|
/// <see cref="RecipeClicked"/> so the parent can update the
|
||||||
|
/// description and output panel.
|
||||||
|
/// </summary>
|
||||||
|
public void OnClick()
|
||||||
|
{
|
||||||
|
if (selected != null)
|
||||||
|
selected.background.Modulate = normalColor;
|
||||||
|
background.Modulate = selectedColor;
|
||||||
|
selected = this;
|
||||||
|
RecipeClicked?.Invoke(recipe, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using Cthangover.Core.Scenes;
|
||||||
|
using Cthangover.Core.Actions.Atomic;
|
||||||
|
using Cthangover.Core.Actions;
|
||||||
|
using Cthangover.Core.Utils;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mods.Cooking.Workbench
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Scenario action registered under <c>toggle_cooking_workbench</c>
|
||||||
|
/// that toggles the cooking workbench UI panel on the scenario screen.
|
||||||
|
/// Called from scenario script directives via the action system.
|
||||||
|
/// Locates the <c>ModLastPanel</c> root node in the scene tree,
|
||||||
|
/// finds the <c>CookingWorkbenchPanel</c> child (a <see cref="WorkbenchPanel"/>),
|
||||||
|
/// and calls <c>Switch()</c> to show or hide it. No-op if the root
|
||||||
|
/// or panel node is missing.
|
||||||
|
/// </summary>
|
||||||
|
public class ToggleCookingPanelAction : IScenarioAction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique action name used in scenario script commands
|
||||||
|
/// (e.g. <c>action toggle_cooking_workbench</c>).
|
||||||
|
/// </summary>
|
||||||
|
public string Name => "toggle_cooking_workbench";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the toggle: finds the <c>CookingWorkbenchPanel</c>
|
||||||
|
/// under <c>ModLastPanel</c> and calls <c>Switch()</c> to
|
||||||
|
/// alternate its visibility state.
|
||||||
|
/// </summary>
|
||||||
|
public void Run(IActionContext ctx)
|
||||||
|
{
|
||||||
|
GameLogger.Log("COOKING", "ToggleCookingPanelAction.Run: looking for CookingWorkbenchPanel...");
|
||||||
|
var root = SceneContextNode.FindNode<Control>("ModLastPanel");
|
||||||
|
if (root == null)
|
||||||
|
{
|
||||||
|
GameLogger.Log("COOKING", "ToggleCookingPanelAction.Run: ModLastPanel NOT FOUND", LogLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var panel = root.GetNodeOrNull<WorkbenchPanel>("CookingWorkbenchPanel");
|
||||||
|
if (panel == null)
|
||||||
|
{
|
||||||
|
GameLogger.Log("COOKING", "ToggleCookingPanelAction.Run: creating panel via TscnScenes.LoadAndBuild...");
|
||||||
|
var loaded = TscnScenes.LoadAndBuild("scenes/cooking_panel.tscn");
|
||||||
|
if (loaded == null)
|
||||||
|
{
|
||||||
|
GameLogger.Log("COOKING", "ToggleCookingPanelAction.Run: LoadAndBuild returned NULL", LogLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var control = loaded as Control;
|
||||||
|
if (control == null) return;
|
||||||
|
control.Name = "CookingWorkbenchPanel";
|
||||||
|
control.Visible = false;
|
||||||
|
control.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
|
||||||
|
root.AddChild(control);
|
||||||
|
panel = control as WorkbenchPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameLogger.Log("COOKING", $"ToggleCookingPanelAction.Run: switching panel (currently Visible={panel?.Visible})");
|
||||||
|
panel?.Switch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Cthangover.Core.Audio;
|
||||||
|
using Cthangover.Core.Factories.Impls;
|
||||||
|
using Cthangover.Core.Items;
|
||||||
|
using Cthangover.Core.Scenes;
|
||||||
|
using Cthangover.Core.Settings;
|
||||||
|
using Cthangover.Core.UI;
|
||||||
|
using Cthangover.Core.UI.Tool;
|
||||||
|
using Cthangover.Core.UI.Inventory;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Mods.Cooking.Workbench
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main cooking workbench UI panel that lists available recipes,
|
||||||
|
/// shows descriptions and output previews, and performs the actual
|
||||||
|
/// cooking operation. Recipes are loaded from <see cref="RecipeData"/>
|
||||||
|
/// filtered by <see cref="WorkbenchType.Cooking"/> and sorted so that
|
||||||
|
/// craftable recipes (all ingredients present) appear before
|
||||||
|
/// un-craftable ones. Selecting a recipe displays its translated
|
||||||
|
/// description and output items via an <see cref="InventoryBagBehaviour"/>.
|
||||||
|
/// Cooking consumes ingredients, produces output items, advances
|
||||||
|
/// in-game time, and plays a sound effect.
|
||||||
|
/// </summary>
|
||||||
|
public class WorkbenchPanel : Widget
|
||||||
|
{
|
||||||
|
private Control content;
|
||||||
|
private RichTextLabel txtDescription;
|
||||||
|
private InventoryBagBehaviour outputContainer;
|
||||||
|
|
||||||
|
private List<RecipeItemBehaviour> recipes = new();
|
||||||
|
|
||||||
|
protected override void OnceConstruct()
|
||||||
|
{
|
||||||
|
MouseFilter = MouseFilterEnum.Ignore;
|
||||||
|
|
||||||
|
var leftTitle = GetNode<Label>("HBox/LeftPanel/LeftTitle");
|
||||||
|
leftTitle.Text = TranslationServer.Translate("ui/cook/title");
|
||||||
|
leftTitle.AddThemeFontSizeOverride("font_size", 16);
|
||||||
|
|
||||||
|
content = GetNode<VBoxContainer>("HBox/LeftPanel/Scroll/Content");
|
||||||
|
|
||||||
|
txtDescription = GetNode<RichTextLabel>("HBox/RightPanel/TxtDescription");
|
||||||
|
|
||||||
|
outputContainer = new InventoryBagBehaviour();
|
||||||
|
outputContainer.SizeFlagsVertical = SizeFlags.ExpandFill;
|
||||||
|
outputContainer.Visible = false;
|
||||||
|
GetNode("HBox/RightPanel").AddChild(outputContainer);
|
||||||
|
|
||||||
|
var btnCook = GetNode<Button>("HBox/RightPanel/BtnCook");
|
||||||
|
btnCook.Text = TranslationServer.Translate("ui/cook/cook");
|
||||||
|
btnCook.Pressed += OnCookClick;
|
||||||
|
|
||||||
|
var btnClose = GetNode<Button>("HBox/RightPanel/BtnClose");
|
||||||
|
btnClose.Text = TranslationServer.Translate("ui/cook/close");
|
||||||
|
btnClose.Pressed += OnCloseClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ShowConstruct()
|
||||||
|
{
|
||||||
|
foreach (var recipe in GetSortedList())
|
||||||
|
AddItem(recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HideDestruct()
|
||||||
|
{
|
||||||
|
foreach (var recipeItem in recipes)
|
||||||
|
if (recipeItem != null)
|
||||||
|
recipeItem.QueueFree();
|
||||||
|
recipes.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddItem(IRecipe recipe)
|
||||||
|
{
|
||||||
|
var item = (RecipeItemBehaviour)TscnScenes.LoadAndBuild("scenes/recipe_item.tscn");
|
||||||
|
if (item != null && content != null)
|
||||||
|
{
|
||||||
|
content.AddChild(item);
|
||||||
|
item.Init(recipe, this);
|
||||||
|
item.RecipeClicked += ClickRecipe;
|
||||||
|
recipes.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IRecipe> GetSortedList()
|
||||||
|
{
|
||||||
|
var list = GameData.Instance.Runtime.RecipeData.GetRecipesByType(WorkbenchType.Cooking);
|
||||||
|
var inventory = GameData.Instance.Runtime.Inventory;
|
||||||
|
list.Sort((o1, o2) =>
|
||||||
|
{
|
||||||
|
var hasNotItems1 = o1.Input.Any(o => inventory.CheckCount(o.Item) < o.Count);
|
||||||
|
var hasNotItems2 = o2.Input.Any(o => inventory.CheckCount(o.Item) < o.Count);
|
||||||
|
if (hasNotItems1 == hasNotItems2)
|
||||||
|
return string.Compare(o1.ID, o2.ID, StringComparison.Ordinal);
|
||||||
|
return hasNotItems1 ? 1 : -1;
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Responds to a recipe being selected in the list.
|
||||||
|
/// Updates the description area with the recipe's translated name
|
||||||
|
/// (styled bold/yellow via BBCode) and description, and populates
|
||||||
|
/// the output preview panel with items the recipe produces by
|
||||||
|
/// wrapping each output into an <see cref="ItemContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void ClickRecipe(IRecipe recipe, RecipeItemBehaviour recipeItemBehaviour)
|
||||||
|
{
|
||||||
|
if (txtDescription != null)
|
||||||
|
txtDescription.Text = "[b][color=yellow]" + TranslationServer.Translate(recipe.Name) + "[/color][/b]\n\n" +
|
||||||
|
TranslationServer.Translate(recipe.Description);
|
||||||
|
|
||||||
|
outputContainer.List = recipe.Output.Select(o => (IItemContainer)new ItemContainer
|
||||||
|
{
|
||||||
|
Item = o.Item,
|
||||||
|
Count = o.Count,
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the workbench panel and plays a UI close-click sound
|
||||||
|
/// via the scene's <see cref="AudioService"/> node.
|
||||||
|
/// </summary>
|
||||||
|
public void OnCloseClick()
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
var audioService = SceneContextNode.FindNode<AudioService>("AudioService");
|
||||||
|
audioService?.PlaySound("ui/close_click", SoundType.UI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the cooking operation for the currently selected recipe.
|
||||||
|
/// Validates that the player has enough ingredients in inventory
|
||||||
|
/// (short-circuits if not). Deducts input items, adds output items,
|
||||||
|
/// advances in-game time by the recipe's duration, updates the
|
||||||
|
/// on-screen time display, plays the cooking sound effect,
|
||||||
|
/// and refreshes all recipe rows so their availability colours
|
||||||
|
/// reflect the new inventory state.
|
||||||
|
/// </summary>
|
||||||
|
public void OnCookClick()
|
||||||
|
{
|
||||||
|
var inventory = GameData.Instance.Runtime.Inventory;
|
||||||
|
var receipt = RecipeItemBehaviour.SelectedRecipe;
|
||||||
|
if (receipt == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var hasItems = !receipt.Input.Any(o => inventory.CheckCount(o.Item) < o.Count);
|
||||||
|
if (!hasItems)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var item in receipt.Input)
|
||||||
|
inventory.Remove(item.Item, item.Count);
|
||||||
|
foreach (var item in receipt.Output)
|
||||||
|
inventory.Add(item.Item, item.Count);
|
||||||
|
|
||||||
|
GameData.Instance.Runtime.Time.AddTime(0, 0, 0, 0, receipt.Time);
|
||||||
|
var timer = SceneContextNode.FindNode<TimeController>("TimeController");
|
||||||
|
timer?.UpdateRenderedTime();
|
||||||
|
|
||||||
|
var audioService = SceneContextNode.FindNode<AudioService>("AudioService");
|
||||||
|
audioService?.PlaySound("ui/cook", SoundType.UI);
|
||||||
|
|
||||||
|
foreach (var recipe in recipes)
|
||||||
|
recipe.UpdateState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
var modPanel = FindNode<Control>("ModLastPanel");
|
||||||
|
if (modPanel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (modPanel.GetNodeOrNull("CookingWorkbenchPanel") != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var panel = Cthangover.Core.Scenes.TscnScenes.LoadAndBuild("scenes/cooking_panel.tscn");
|
||||||
|
if (panel is Control control)
|
||||||
|
{
|
||||||
|
control.Name = "CookingWorkbenchPanel";
|
||||||
|
control.Visible = false;
|
||||||
|
control.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
|
||||||
|
modPanel.AddChild(control);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
var modPanel = FindNode<Control>("Lastground");
|
||||||
|
if (modPanel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dinner = modPanel.GetNodeOrNull<Mods.Cooking.Rations.RationStatisticsPanel>("RationStatistics");
|
||||||
|
if (dinner != null)
|
||||||
|
dinner.QueueFree();
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
var modPanel = FindNode<Control>("Lastground");
|
||||||
|
if (modPanel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dinner = modPanel.GetNodeOrNull<Mods.Cooking.Rations.RationStatisticsPanel>("RationStatistics");
|
||||||
|
if (dinner == null)
|
||||||
|
{
|
||||||
|
dinner = (Mods.Cooking.Rations.RationStatisticsPanel)Cthangover.Core.Scenes.TscnScenes.LoadAndBuild("scenes/ration_statistics_panel.tscn");
|
||||||
|
dinner.Name = "RationStatistics";
|
||||||
|
dinner.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
|
||||||
|
dinner.CustomMinimumSize = new Vector2(300, 300);
|
||||||
|
modPanel.AddChild(dinner);
|
||||||
|
|
||||||
|
dinner.Show();
|
||||||
|
modPanel.UpdateMinimumSize();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user