P1 iron: nuggets, ore, clay, kiln, smelter, forge, iron tools and arrows, lantern, tool wear and repair
This commit is contained in:
@@ -81,6 +81,81 @@ func tell_all(text: String) -> void:
|
||||
else:
|
||||
notify.emit(text)
|
||||
|
||||
# --- wear and repair (P1, docs/PROGRESSION_DESIGN.md §2) ---------------------------
|
||||
|
||||
## One use of the tool or weapon in the player's hand: it wears, and breaks
|
||||
## when it runs out. Things without "durability" never wear.
|
||||
func use_held(player: Node) -> void:
|
||||
var slot: int = player.selected
|
||||
var s = player.inventory.get_slot(slot)
|
||||
if s == null:
|
||||
return
|
||||
var full := ItemDB.max_durability(s.id)
|
||||
if full <= 0:
|
||||
return
|
||||
var left := ItemDB.durability(s) - 1
|
||||
if left <= 0:
|
||||
player.inventory.set_slot(slot, null)
|
||||
tell(player, "Your %s broke" % ItemDB.item_name(s.id).to_lower())
|
||||
return
|
||||
var st: Dictionary = s.duplicate()
|
||||
st.dur = left
|
||||
player.inventory.set_slot(slot, st)
|
||||
if left == int(full * 0.1) or left == 5:
|
||||
tell(player, "Your %s is nearly worn out (repair it at a %s)" % [ItemDB.item_name(s.id).to_lower(),
|
||||
String(ItemDB.get_item(s.id).get("repair", {}).get("station", "workbench"))])
|
||||
|
||||
## What repairing the item in `slot` costs, and where: {"inputs", "station"},
|
||||
## or {} if it can't be repaired or doesn't need it.
|
||||
func repair_cost(player: Node, slot: int) -> Dictionary:
|
||||
var s = player.inventory.get_slot(slot)
|
||||
if s == null:
|
||||
return {}
|
||||
var r: Dictionary = ItemDB.get_item(s.id).get("repair", {})
|
||||
if r.is_empty() or ItemDB.durability(s) >= ItemDB.max_durability(s.id):
|
||||
return {}
|
||||
return r
|
||||
|
||||
func can_repair(player: Node, slot: int) -> bool:
|
||||
var r := repair_cost(player, slot)
|
||||
return not r.is_empty() and player.inventory.has_all(r.inputs) and station_near(player, String(r.get("station", "")))
|
||||
|
||||
## Brings the item in `slot` back to as new, for a little of what it's made of.
|
||||
func repair(player: Node, slot: int) -> bool:
|
||||
if _online():
|
||||
return _ask("repair", [slot])
|
||||
var r := repair_cost(player, slot)
|
||||
if r.is_empty():
|
||||
return false
|
||||
if not station_near(player, String(r.get("station", ""))):
|
||||
notify.emit("Needs a %s nearby" % r.station)
|
||||
return false
|
||||
if not player.inventory.has_all(r.inputs):
|
||||
notify.emit("Missing materials")
|
||||
return false
|
||||
var s: Dictionary = player.inventory.get_slot(slot).duplicate()
|
||||
for id in r.inputs:
|
||||
player.inventory.remove(id, int(r.inputs[id]))
|
||||
s.erase("dur")
|
||||
# (Removing materials can't move the tool: it isn't one of them.)
|
||||
player.inventory.set_slot(slot, s)
|
||||
notify.emit("Repaired your %s" % ItemDB.item_name(s.id).to_lower())
|
||||
return true
|
||||
|
||||
## The arrows a bow shoots: the first kind in its "ammo" list you have.
|
||||
func ammo_for(player: Node, bow: Dictionary) -> String:
|
||||
var kinds: Array = bow.ammo if bow.ammo is Array else [bow.ammo]
|
||||
for k in kinds:
|
||||
if player.inventory.count(String(k)) > 0:
|
||||
return String(k)
|
||||
return ""
|
||||
|
||||
func ammo_count(player: Node, bow: Dictionary) -> int:
|
||||
var n := 0
|
||||
for k in (bow.ammo if bow.ammo is Array else [bow.ammo]):
|
||||
n += player.inventory.count(String(k))
|
||||
return n
|
||||
|
||||
## Online, a creature is known by its net id (creature_net.gd).
|
||||
static func _cid(c: Node) -> int:
|
||||
return int(c.get_meta("cid", 0))
|
||||
@@ -140,7 +215,17 @@ func pickup(player: Node3D, pickup_node: Node3D) -> bool:
|
||||
if _online():
|
||||
return _ask("pickup", [WorldNet.pickup_key(pickup_node)]) # first come, first served
|
||||
var id: String = pickup_node.item_id
|
||||
if _give(player, id, pickup_node.amount) <= 0:
|
||||
var extra: Dictionary = pickup_node.get("extra") if pickup_node.get("extra") != null else {}
|
||||
if not extra.is_empty():
|
||||
var st := extra.duplicate()
|
||||
st.id = id
|
||||
st.count = pickup_node.amount
|
||||
if player.inventory.add_stack(st) > 0:
|
||||
notify.emit("Inventory full")
|
||||
return false
|
||||
item_gained.emit(id, pickup_node.amount)
|
||||
notify.emit("+%d %s" % [pickup_node.amount, ItemDB.item_name(id)])
|
||||
elif _give(player, id, pickup_node.amount) <= 0:
|
||||
return false
|
||||
pickup_node.collected()
|
||||
return true
|
||||
@@ -164,6 +249,7 @@ func harvest_hit(player: Node3D, node: Node3D, hit_point: Vector3) -> bool:
|
||||
var drops: Dictionary = node.hit(float(tool.get("damage", 1.0)), hit_point, player.global_position)
|
||||
for id in drops:
|
||||
_give(player, id, int(drops[id]))
|
||||
use_held(player)
|
||||
return true
|
||||
|
||||
func can_place(player: Node3D, slot: int) -> bool:
|
||||
@@ -283,6 +369,7 @@ func melee(player: Node3D, target: Node3D) -> bool:
|
||||
notify.emit("Too tired")
|
||||
return false
|
||||
target.take_hit(m.damage, player.global_position)
|
||||
use_held(player)
|
||||
if target.is_dead():
|
||||
notify.emit("Killed a %s" % target.display_name().to_lower())
|
||||
return true
|
||||
@@ -316,6 +403,7 @@ func hit_player(player: Node3D, peer: int) -> bool:
|
||||
return false
|
||||
player.melee_ready_at = Time.get_ticks_msec() + int(m.cooldown * 1000.0 * 0.8)
|
||||
target.survival.damage(m.damage, player.global_position, String(player.display))
|
||||
use_held(player)
|
||||
notify.emit("Hit %s" % String(target.display))
|
||||
return true
|
||||
|
||||
@@ -342,9 +430,16 @@ func throw_spear(player: Node3D, slot: int, origin: Vector3, dir: Vector3, world
|
||||
notify.emit("Too tired")
|
||||
return false
|
||||
var id: String = s.id
|
||||
var left := ItemDB.durability(s) - 1
|
||||
player.inventory.take_from(slot, 1)
|
||||
world.launch(id, origin, dir.normalized() * float(w.throw.speed) + player.velocity * 0.5,
|
||||
var pr: Node3D = world.launch(id, origin, dir.normalized() * float(w.throw.speed) + player.velocity * 0.5,
|
||||
float(w.throw.damage), player)
|
||||
if ItemDB.max_durability(id) > 0:
|
||||
if left <= 0:
|
||||
pr.breaks = true # (its last throw: it breaks where it lands)
|
||||
tell(player, "Your %s will break" % ItemDB.item_name(id).to_lower())
|
||||
else:
|
||||
pr.extra = {"dur": left}
|
||||
return true
|
||||
|
||||
## Starts drawing the held bow: needs arrows and a little stamina.
|
||||
@@ -354,13 +449,13 @@ func can_draw(player: Node3D) -> bool:
|
||||
# (The server checks arrows and spends the stamina when you shoot.)
|
||||
if bow.is_empty():
|
||||
return false
|
||||
if player.inventory.count(bow.ammo) <= 0:
|
||||
if ammo_count(player, bow) <= 0:
|
||||
notify.emit("No arrows (craft them at a workbench: sticks, flint, feathers)")
|
||||
return false
|
||||
return true
|
||||
if bow.is_empty():
|
||||
return false
|
||||
if player.inventory.count(bow.ammo) <= 0:
|
||||
if ammo_count(player, bow) <= 0:
|
||||
notify.emit("No arrows (craft them at a workbench: sticks, flint, feathers)")
|
||||
return false
|
||||
if not player.survival.use_stamina(float(bow.stamina)):
|
||||
@@ -375,12 +470,15 @@ func shoot(player: Node3D, origin: Vector3, dir: Vector3, power: float, world: N
|
||||
var bow: Dictionary = ItemDB.get_item(player.held_item_id()).get("bow", {})
|
||||
if bow.is_empty() or player.global_position.distance_to(origin) > 2.5:
|
||||
return false
|
||||
if not player.inventory.remove(bow.ammo, 1):
|
||||
var ammo := ammo_for(player, bow)
|
||||
if ammo.is_empty() or not player.inventory.remove(ammo, 1):
|
||||
return false
|
||||
power = clampf(power, 0.0, 1.0)
|
||||
var spd: float = lerpf(float(bow.speed[0]), float(bow.speed[1]), power)
|
||||
var dmg: float = lerpf(float(bow.damage[0]), float(bow.damage[1]), power)
|
||||
world.launch(bow.ammo, origin, dir.normalized() * spd, dmg, player)
|
||||
dmg *= float(ItemDB.get_item(ammo).get("ammo", {}).get("damage_mult", 1.0))
|
||||
world.launch(ammo, origin, dir.normalized() * spd, dmg, player)
|
||||
use_held(player)
|
||||
return true
|
||||
|
||||
func butcher(player: Node3D, carcass: Node3D) -> bool:
|
||||
@@ -417,6 +515,7 @@ func cast_line(player: Node3D, origin: Vector3, dir: Vector3, world: Node3D) ->
|
||||
var d := dir.normalized()
|
||||
d.y = maxf(d.y, -0.2) + 0.35 # a lobbed cast, even when looking level
|
||||
world.start_fishing(player, origin, d.normalized() * float(rod.get("cast_speed", 12.0)))
|
||||
use_held(player)
|
||||
return true
|
||||
|
||||
## Reels in. With a fish biting you land it; otherwise the line just comes in.
|
||||
|
||||
@@ -31,10 +31,27 @@ static func _stack_max(id: String) -> int:
|
||||
var db := (Engine.get_main_loop() as SceneTree).root.get_node_or_null("ItemDB")
|
||||
return db.stack_size(id) if db != null else 50
|
||||
|
||||
## A stack can carry "dur": uses left on a tool or weapon that wears out
|
||||
## (ItemDB "durability"). No "dur" means as new.
|
||||
static func _norm(stack):
|
||||
if stack == null or int(stack.count) <= 0:
|
||||
return null
|
||||
return {"id": String(stack.id), "count": int(stack.count)}
|
||||
var out := {"id": String(stack.id), "count": int(stack.count)}
|
||||
if stack.has("dur"):
|
||||
out.dur = int(stack.dur)
|
||||
return out
|
||||
|
||||
## Adds a whole stack, keeping what's special about it (a worn tool's "dur").
|
||||
## Returns how many didn't fit.
|
||||
func add_stack(stack: Dictionary) -> int:
|
||||
if not stack.has("dur"):
|
||||
return add(String(stack.id), int(stack.count))
|
||||
for i in range(slots.size()):
|
||||
if slots[i] == null:
|
||||
slots[i] = _norm(stack)
|
||||
changed.emit()
|
||||
return 0
|
||||
return int(stack.count)
|
||||
|
||||
func count(id: String) -> int:
|
||||
var n := 0
|
||||
@@ -117,8 +134,8 @@ func quick_move(i: int, other: Inventory) -> void:
|
||||
var s = slots[i]
|
||||
if s == null:
|
||||
return
|
||||
var left := other.add(s.id, s.count)
|
||||
slots[i] = null if left <= 0 else {"id": s.id, "count": left}
|
||||
var left := other.add_stack(s)
|
||||
slots[i] = null if left <= 0 else _norm({"id": s.id, "count": left, "dur": s.dur} if s.has("dur") else {"id": s.id, "count": left})
|
||||
changed.emit()
|
||||
|
||||
## Clicking slot `i` while holding `cursor` (a stack or null). Returns the new
|
||||
@@ -138,7 +155,7 @@ func click(i: int, cursor, right: bool):
|
||||
result = s
|
||||
slots[i] = null
|
||||
elif s == null:
|
||||
if right:
|
||||
if right and cursor.count > 1:
|
||||
slots[i] = {"id": cursor.id, "count": 1}
|
||||
result = _norm({"id": cursor.id, "count": cursor.count - 1})
|
||||
else:
|
||||
|
||||
@@ -109,6 +109,16 @@ func item_name(id: String) -> String:
|
||||
func stack_size(id: String) -> int:
|
||||
return int(items.get(id, {}).get("stack", 1))
|
||||
|
||||
## How many uses a tool or weapon has when new (0 = it never wears out).
|
||||
func max_durability(id: String) -> int:
|
||||
return int(items.get(id, {}).get("durability", 0))
|
||||
|
||||
## Uses left on a stack (its max if it's never been used, or it can't wear).
|
||||
func durability(stack) -> int:
|
||||
if stack == null:
|
||||
return 0
|
||||
return int(stack.get("dur", max_durability(stack.id)))
|
||||
|
||||
func mesh_for(id: String) -> ArrayMesh:
|
||||
return ItemMeshes.build(items.get(id, {}).get("mesh", id))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user