İlk commit: Stepstead Godot istemci (4.6 Mobile)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jts 2026-07-11 11:31:35 +03:00
commit 6a1706051d
346 changed files with 14415 additions and 0 deletions

210
scripts/location_panel.gd Normal file
View file

@ -0,0 +1,210 @@
extends PanelContainer
## Lokasyon paneli — bulunduğun yerin kaynakları (topla butonları), buradaki oyuncular.
## Kod ile kurulur, world.gd HUD'a ekler. Node adı "LocationPanel".
const PANEL_HEIGHT := 620.0
signal city_requested
var _city_btn: Button
var _title: Label
var _desc: Label
var _players: Label
var _list: VBoxContainer
var _scroll: ScrollContainer
var _header: TextureRect
func _ready() -> void:
name = "LocationPanel"
visible = false
_build_ui()
func _build_ui() -> void:
set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
offset_top = -PANEL_HEIGHT
offset_left = 8
offset_right = -8
offset_bottom = -8
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0.10, 0.12, 0.10, 0.97)
sb.set_corner_radius_all(10)
add_theme_stylebox_override("panel", sb)
var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 14)
margin.add_theme_constant_override("margin_right", 14)
margin.add_theme_constant_override("margin_top", 10)
margin.add_theme_constant_override("margin_bottom", 10)
add_child(margin)
var vbox := VBoxContainer.new()
vbox.add_theme_constant_override("separation", 6)
margin.add_child(vbox)
# Lokasyon başlık görseli (assets/locations/<code>.webp — varsa)
_header = TextureRect.new()
_header.custom_minimum_size = Vector2(0, 190)
_header.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
_header.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
_header.clip_contents = true
_header.visible = false
vbox.add_child(_header)
var top := HBoxContainer.new()
_title = Label.new()
_title.add_theme_font_size_override("font_size", 20)
_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(_title)
_city_btn = Button.new()
_city_btn.text = "🏙 Şehir Haritası"
_city_btn.visible = false
_city_btn.pressed.connect(func() -> void:
visible = false
city_requested.emit())
top.add_child(_city_btn)
var close_btn := Button.new()
close_btn.text = ""
close_btn.pressed.connect(func() -> void: visible = false)
top.add_child(close_btn)
vbox.add_child(top)
_desc = Label.new()
_desc.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_desc.add_theme_font_size_override("font_size", 13)
_desc.add_theme_color_override("font_color", Color(0.7, 0.72, 0.65))
vbox.add_child(_desc)
_players = Label.new()
_players.add_theme_font_size_override("font_size", 13)
_players.add_theme_color_override("font_color", Color(0.55, 0.75, 0.95))
vbox.add_child(_players)
_scroll = ScrollContainer.new()
_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
vbox.add_child(_scroll)
_list = VBoxContainer.new()
_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_list.add_theme_constant_override("separation", 6)
_scroll.add_child(_list)
func open() -> void:
visible = true
await refresh()
func refresh() -> void:
var res: Dictionary = await GameState.load_location()
if not res.ok or not (res.body is Dictionary):
_title.text = "Lokasyon yüklenemedi"
return
var loc: Dictionary = res.body
_title.text = "📍 %s" % str(loc.get("name", ""))
_city_btn.visible = str(loc.get("type", "")) in ["city", "village"]
_desc.text = str(loc.get("description", ""))
var img_path := "res://assets/locations/%s.webp" % str(loc.get("code", ""))
if ResourceLoader.exists(img_path):
_header.texture = load(img_path)
_header.visible = true
else:
_header.visible = false
var names: Array = loc.get("players_here", [])
_players.text = "👥 Burada: %s" % ", ".join(names) if not names.is_empty() else "👥 Burada başka kimse yok"
for c in _list.get_children():
c.queue_free()
_list.add_child(_camp_row(loc))
var resources: Array = loc.get("resources", [])
if resources.is_empty():
var l := Label.new()
l.text = "Burada toplanacak kaynak yok."
l.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
_list.add_child(l)
return
var bal := int(GameState.character.get("step_balance", 0))
for r in resources:
if r is Dictionary:
_list.add_child(_resource_row(r, bal))
const SKILL_TR := {"mining": "Madencilik", "fishing": "Balıılık", "foraging": "Toplama",
"woodcutting": "Ağaç Kesme", "hunting": "Avcılık"}
func _resource_row(r: Dictionary, bal: int) -> HBoxContainer:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 10)
var info := VBoxContainer.new()
info.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var name_lbl := Label.new()
name_lbl.text = str(r.get("item_name", ""))
name_lbl.add_theme_font_size_override("font_size", 15)
info.add_child(name_lbl)
var meta := Label.new()
var skill: String = str(r.get("skill", ""))
meta.text = "%s%d👣 • +%dXP" % [SKILL_TR.get(skill, skill), int(r.get("step_cost", 0)), int(r.get("xp", 0))]
meta.add_theme_font_size_override("font_size", 12)
meta.add_theme_color_override("font_color", Color(0.6, 0.62, 0.58))
info.add_child(meta)
row.add_child(info)
var btn := Button.new()
var cd := int(r.get("cooldown_remaining", 0))
var tool_ok: bool = bool(r.get("tool_ok", true))
var cost := int(r.get("step_cost", 0))
if not tool_ok:
btn.text = "🔒 %s gerekli" % str(r.get("tool_needed", "Tool"))
btn.disabled = true
elif cd > 0:
btn.text = "%ds" % cd
btn.disabled = true
elif bal < cost:
btn.text = "👣 yetersiz"
btn.disabled = true
else:
btn.text = "Topla"
var code := str(r.get("item_code", ""))
btn.pressed.connect(_on_gather.bind(btn, code))
row.add_child(btn)
return row
const CAMP_ITEM_TR := {"log_oak": "Meşe Odunu", "stone_small": "Küçük Taş", "vine": "Sarmaşık"}
func _camp_row(loc: Dictionary) -> HBoxContainer:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 10)
var lbl := Label.new()
lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
lbl.add_theme_font_size_override("font_size", 13)
if bool(loc.get("has_camp", false)):
lbl.text = "⛺ Kampın var — toplama maliyeti -%25"
lbl.add_theme_color_override("font_color", Color(0.6, 0.85, 0.6))
row.add_child(lbl)
return row
var cost_items: Dictionary = loc.get("camp_cost_items", {})
var parts: Array[String] = []
for code in cost_items:
parts.append("%d %s" % [int(cost_items[code]), str(CAMP_ITEM_TR.get(code, code))])
lbl.text = "⛺ Kamp: %s + %d👣 (-%%25 maliyet)" % [", ".join(parts), int(loc.get("camp_step_cost", 100))]
lbl.add_theme_color_override("font_color", Color(0.75, 0.7, 0.55))
row.add_child(lbl)
var btn := Button.new()
btn.text = "Kur"
btn.pressed.connect(_on_build_camp.bind(btn))
row.add_child(btn)
return row
func _on_build_camp(btn: Button) -> void:
btn.disabled = true
var res: Dictionary = await GameState.build_camp()
if not res.ok and res.body is Dictionary and res.body.has("detail"):
print("[camp fail] ", str(res.body["detail"]))
await refresh()
func _on_gather(btn: Button, code: String) -> void:
btn.disabled = true
var res: Dictionary = await GameState.gather(code)
if not res.ok:
var msg := "Toplanamadı"
if res.body is Dictionary and res.body.has("detail"):
msg = str(res.body["detail"])
print("[gather fail] ", msg)
await refresh()