M1d: creatures, hunting, weapons, food buffs and the hide cloak

- Rabbits, deer and boars with procedural meshes and animated legs, a
  state machine (idle/wander/flee/charge/dead) and heightmap steering that
  avoids water, cliffs, the world edge, trees and buildings
- Spawner keeps a habitat-based budget around the player (6/4/3)
- Melee needs a weapon or tool (club, spear, axe, pickaxe); box sweep hits
- Wooden spear (thrust or throw) and crude bow with draw-scaled arrows;
  projectiles stick in the world as saved pickups
- Carcasses: E to butcher for meat, hide and embedded arrows
- Boars charge and gore when hit; hurt flash and a brief healing pause
- Cooked meat gives Well fed (faster stamina and healing); buffs saved
- Hide cloak: wear for warmth, saved, goes in the grave on death
- Feathers as meadow pickups (appended, existing save ids unchanged)
- New test_creatures_combat suite; run_all.sh gets a per-suite time limit
This commit is contained in:
Paul Bailey
2026-11-04 10:55:44 +00:00
parent fed96ef118
commit 67bf85523d
22 changed files with 1782 additions and 33 deletions

View File

@@ -18,6 +18,13 @@ var _menu: Control
var _bars: Dictionary = {}
var _status: Label
var _clock: Label
var _hurt: ColorRect
## A red flash at the screen edge when something hits you.
func _on_hurt(amount: float, _from: Vector3) -> void:
_hurt.color.a = clampf(0.15 + amount / 60.0, 0.15, 0.45)
var tw := _hurt.create_tween()
tw.tween_property(_hurt, "color:a", 0.0, 0.6)
func bind(p: CharacterBody3D) -> void:
player = p
@@ -93,6 +100,13 @@ func bind(p: CharacterBody3D) -> void:
player.selected_changed.connect(_refresh)
player.inventory_requested.connect(_on_inventory_requested)
player.hands.prompt_changed.connect(func(t): _prompt.text = t)
_hurt = ColorRect.new()
_hurt.color = Color(0.7, 0.0, 0.0, 0.0)
_hurt.set_anchors_preset(Control.PRESET_FULL_RECT)
_hurt.mouse_filter = Control.MOUSE_FILTER_IGNORE
root.add_child(_hurt)
root.move_child(_hurt, 0)
player.survival.hurt.connect(_on_hurt)
Actions.notify.connect(toast)
ItemDB.icons_ready.connect(_refresh)
_refresh()

View File

@@ -21,6 +21,8 @@ var _chest_grid: GridContainer
var _chest_title: Label
var _recipe_list: VBoxContainer
var _station_label: Label
var _worn_label: Label
var _worn_btn: Button
var _cursor = null ## stack held on the mouse
var _cursor_view: Control
var _cursor_icon: TextureRect
@@ -73,6 +75,19 @@ func bind(p: CharacterBody3D) -> void:
pass
pv.add_child(grid)
pv.add_child(UITheme.label("Row 1 is your hotbar. Shift-click moves stacks to/from a chest.", 13, UITheme.TEXT_DIM))
var wr := HBoxContainer.new()
wr.add_theme_constant_override("separation", 10)
_worn_label = UITheme.label("", 15)
wr.add_child(_worn_label)
_worn_btn = Button.new()
_worn_btn.text = "Take off"
_worn_btn.focus_mode = Control.FOCUS_NONE
_worn_btn.pressed.connect(func():
Actions.take_off(player)
_refresh())
wr.add_child(_worn_btn)
pv.add_child(wr)
player.worn_changed.connect(_refresh)
pp.add_child(pv)
row.add_child(pp)
@@ -203,6 +218,8 @@ func _refresh() -> void:
if _cursor != null:
_cursor_icon.texture = ItemDB.icon(_cursor.id)
_cursor_count.text = str(_cursor.count) if _cursor.count > 1 else ""
_worn_label.text = "Wearing: %s" % (ItemDB.item_name(player.worn) if not player.worn.is_empty() else "nothing")
_worn_btn.visible = not player.worn.is_empty()
_rebuild_recipes()
func _rebuild_recipes() -> void:

View File

@@ -8,7 +8,8 @@ const UITheme = preload("res://scripts/ui/theme.gd")
const CONTROLS := [
["W A S D", "Move"], ["Shift", "Sprint"], ["Space", "Jump / swim up"],
["Mouse", "Look"], ["E", "Interact / pick up / add fuel"], ["Left click", "Use tool / place"],
["Right click (holding food)", "Eat"],
["Right click (holding food)", "Eat"], ["Right click (spear / cloak)", "Throw / wear"],
["Hold left click (bow)", "Draw, release to shoot"],
["R", "Rotate piece (building pieces: quarter turns)"], ["X", "Dismantle the piece you're looking at"],
["E on a bed", "Set spawn point / sleep (night, under a roof)"],
["1 - 8, wheel", "Hotbar"], ["Tab", "Inventory & crafting (shift-click Craft for 5)"],