0.4.0: map (N) that fills in as you explore and a compass (closes #1); Old Tam's seam and Cove tips (voiced); the journey and sea fish in the guide; trailer with the tunnel and the Cove; protocol 16
This commit is contained in:
@@ -34,6 +34,7 @@ func _ready() -> void:
|
||||
_bind("drop", [KEY_Q]) # (shift+Q drops just one)
|
||||
_bind("row_up", [KEY_K]) # the 1-8 keys pick from the bag row above...
|
||||
_bind("row_down", [KEY_M]) # ...or below
|
||||
_bind("map", [KEY_N]) # the map (Gitea issue #1)
|
||||
for i in range(8):
|
||||
_bind("hotbar_%d" % (i + 1), [KEY_1 + i])
|
||||
_bind_mouse("attack", MOUSE_BUTTON_LEFT)
|
||||
|
||||
@@ -135,4 +135,9 @@ func _met(when: String) -> bool:
|
||||
return not dead and world.day_night.is_night() and world.day_night.wolf_moon_now()
|
||||
"hard_winter":
|
||||
return not dead and world.weather != null and world.weather.in_hard_winter()
|
||||
"worldlet": # (where you are on the journey: "worldlet:cove")
|
||||
return not dead and world.has_method("worldlet_id") and String(world.worldlet_id()) in vals
|
||||
"seam": # (an antler pickaxe in your bag, and the cliff tunnel's seam still to dig)
|
||||
return not dead and world.get("tunnel") != null and not world.tunnel.opened \
|
||||
and player.inventory.count("antler_pickaxe") > 0
|
||||
return false
|
||||
|
||||
@@ -32,7 +32,7 @@ signal clock(day: int, time: float) ## the server's time of day
|
||||
const Identity = preload("res://scripts/net/identity.gd")
|
||||
const ServerConfig = preload("res://scripts/net/server_config.gd")
|
||||
|
||||
const PROTOCOL := 15 ## 3: shared world; 4: the server decides; 5: wildlife; 6: rules and chat (M2.6); 7: the alpha, broken doors; 8: the bear; 9: crows and foxes; 10: the barrow (P3); 11: food spoilage, lightning (P4); 12: the standing stones and the Old Stag (P5); 13: sounds sent to players nearby (sfx.gd); 14: dropping items, any bag row in hand, new items (jar, bedroll, necklace, bear cloak, rug, pemmican); 15: placed lights (candle, torch stand)
|
||||
const PROTOCOL := 16 ## 16: worldlets (the Cove), sea fish (mackerel, cod); 3: shared world; 4: the server decides; 5: wildlife; 6: rules and chat (M2.6); 7: the alpha, broken doors; 8: the bear; 9: crows and foxes; 10: the barrow (P3); 11: food spoilage, lightning (P4); 12: the standing stones and the Old Stag (P5); 13: sounds sent to players nearby (sfx.gd); 14: dropping items, any bag row in hand, new items (jar, bedroll, necklace, bear cloak, rug, pemmican); 15: placed lights (candle, torch stand)
|
||||
const DEFAULT_PORT := 24242
|
||||
const HANDSHAKE_TIMEOUT := 10.0
|
||||
const JOIN_TIMEOUT := 20.0 ## includes the server building its terrain on first join
|
||||
|
||||
53
scripts/ui/compass.gd
Normal file
53
scripts/ui/compass.gd
Normal file
@@ -0,0 +1,53 @@
|
||||
extends Control
|
||||
## The compass (Gitea issue #1): a strip across the top of the screen that
|
||||
## shows which way you're facing, N/E/S/W and the points between, with a tick
|
||||
## at the centre. North is the map's top (-Z).
|
||||
|
||||
const UITheme = preload("res://scripts/ui/theme.gd")
|
||||
const WIDTH := 420.0
|
||||
const SPAN := 180.0 ## degrees shown across the strip
|
||||
const POINTS := [[0, "N"], [45, "NE"], [90, "E"], [135, "SE"], [180, "S"], [225, "SW"], [270, "W"], [315, "NW"]]
|
||||
|
||||
var player: Node3D
|
||||
|
||||
func setup(p: Node3D) -> void:
|
||||
player = p
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
set_anchors_preset(Control.PRESET_CENTER_TOP)
|
||||
offset_left = -WIDTH * 0.5
|
||||
offset_right = WIDTH * 0.5
|
||||
offset_top = 44.0
|
||||
offset_bottom = 74.0
|
||||
|
||||
## Which way `p` faces, in degrees clockwise from north (0 = N, 90 = E).
|
||||
static func heading(p: Node3D) -> float:
|
||||
var f: Vector3 = -p.global_basis.z
|
||||
return fposmod(rad_to_deg(atan2(f.x, -f.z)), 360.0)
|
||||
|
||||
func _process(_d: float) -> void:
|
||||
visible = player != null and is_instance_valid(player) and not bool(player.get("ui_open"))
|
||||
queue_redraw()
|
||||
|
||||
func _draw() -> void:
|
||||
if player == null or not is_instance_valid(player):
|
||||
return
|
||||
var h := heading(player)
|
||||
var font := get_theme_default_font()
|
||||
draw_rect(Rect2(Vector2.ZERO, size), Color(0, 0, 0, 0.35))
|
||||
for deg in range(0, 360, 15):
|
||||
var off := wrapf(deg - h, -180.0, 180.0)
|
||||
if absf(off) > SPAN * 0.5:
|
||||
continue
|
||||
var x := WIDTH * 0.5 + off / SPAN * WIDTH
|
||||
var big := deg % 45 == 0
|
||||
draw_line(Vector2(x, 0), Vector2(x, 6 if not big else 9), Color(1, 1, 1, 0.5 if not big else 0.8), 1.0)
|
||||
for pt in POINTS:
|
||||
var off := wrapf(float(pt[0]) - h, -180.0, 180.0)
|
||||
if absf(off) > SPAN * 0.5 - 6.0:
|
||||
continue
|
||||
var x := WIDTH * 0.5 + off / SPAN * WIDTH
|
||||
var s := 17 if String(pt[1]).length() == 1 else 13
|
||||
var col := Color(1.0, 0.55, 0.35) if pt[1] == "N" else Color(1, 1, 1, 0.9)
|
||||
var w := font.get_string_size(pt[1], HORIZONTAL_ALIGNMENT_CENTER, -1, s).x
|
||||
draw_string(font, Vector2(x - w * 0.5, 25), pt[1], HORIZONTAL_ALIGNMENT_LEFT, -1, s, col)
|
||||
draw_line(Vector2(WIDTH * 0.5, 0), Vector2(WIDTH * 0.5, 30), Color(1.0, 0.85, 0.4, 0.9), 2.0)
|
||||
1
scripts/ui/compass.gd.uid
Normal file
1
scripts/ui/compass.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b86dwl7mmrk2y
|
||||
@@ -21,6 +21,8 @@ var _diet_bars: Dictionary = {}
|
||||
var _status: Label
|
||||
var _clock: Label
|
||||
var _hurt: ColorRect
|
||||
var compass: Control
|
||||
var map: Control
|
||||
|
||||
## A red flash at the screen edge when something hits you.
|
||||
func _on_hurt(amount: float, _from: Vector3) -> void:
|
||||
@@ -196,6 +198,15 @@ func _build_stats(root: Control) -> void:
|
||||
tips.name = "HintCard"
|
||||
root.add_child(tips)
|
||||
tips.setup()
|
||||
# The compass along the top, and the map on N (Gitea issue #1).
|
||||
compass = preload("res://scripts/ui/compass.gd").new()
|
||||
compass.name = "Compass"
|
||||
root.add_child(compass)
|
||||
compass.setup(player)
|
||||
map = preload("res://scripts/ui/map_screen.gd").new()
|
||||
map.name = "Map"
|
||||
root.add_child(map)
|
||||
map.setup(player.get_parent(), player)
|
||||
|
||||
func _process(_d: float) -> void:
|
||||
_fade_chat()
|
||||
@@ -387,7 +398,9 @@ func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_ESCAPE:
|
||||
if is_dead_screen():
|
||||
return # (respawn first)
|
||||
if _screen.is_open():
|
||||
if map != null and map.is_open():
|
||||
map.close()
|
||||
elif _screen.is_open():
|
||||
_screen.close()
|
||||
elif _menu.is_open():
|
||||
_menu.back()
|
||||
@@ -398,6 +411,13 @@ func _input(event: InputEvent) -> void:
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if _menu.is_open():
|
||||
return
|
||||
if event.is_action_pressed("map") and not _screen.is_open() and not is_dead_screen() \
|
||||
and (map.is_open() or not player.ui_open):
|
||||
map.toggle()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if map.is_open():
|
||||
return
|
||||
if event.is_action_pressed("inventory") and not _screen.is_open():
|
||||
_screen.open(null)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
182
scripts/ui/map_screen.gd
Normal file
182
scripts/ui/map_screen.gd
Normal file
@@ -0,0 +1,182 @@
|
||||
extends Control
|
||||
## The map (Gitea issue #1), on N. The worldlet you're in, drawn from its
|
||||
## terrain (water, sand, grass, rock, snow, shaded by slope), but only where
|
||||
## you've been: the rest is blank until you explore it (world.explored).
|
||||
## Marked where you've seen them: the den, the barrow, the standing stones,
|
||||
## the tunnel's seam, the pier and sea cave, your beds and buildings, your
|
||||
## camp (fires, chests, benches) and your grave. You're a dot; which way
|
||||
## you're facing is on the compass (and under the map). North is up.
|
||||
|
||||
const UITheme = preload("res://scripts/ui/theme.gd")
|
||||
const Compass = preload("res://scripts/ui/compass.gd")
|
||||
const RES := 200 ## map image pixels per side
|
||||
const SIDE := 500.0 ## map size on screen
|
||||
|
||||
const MARKS := {
|
||||
"den": [Color(0.85, 0.30, 0.25), "Wolf den"],
|
||||
"barrow": [Color(0.75, 0.70, 0.55), "Barrow"],
|
||||
"stones": [Color(0.80, 0.80, 0.85), "Standing stones"],
|
||||
"seam": [Color(0.95, 0.60, 0.25), "Cliff tunnel"],
|
||||
"pier": [Color(0.70, 0.50, 0.30), "Pier"],
|
||||
"cave": [Color(0.95, 0.60, 0.25), "Sea cave"],
|
||||
"bed": [Color(0.40, 0.75, 1.00), "Bed"],
|
||||
"build": [Color(0.62, 0.45, 0.28), "Your buildings"],
|
||||
"camp": [Color(1.00, 0.80, 0.35), "Camp"],
|
||||
"grave": [Color(0.85, 0.85, 0.85), "Your things"],
|
||||
}
|
||||
|
||||
var world: Node3D
|
||||
var player: Node3D
|
||||
var _tex: TextureRect
|
||||
var _marks: Control
|
||||
var _title: Label
|
||||
var _facing: Label
|
||||
|
||||
func setup(w: Node3D, p: Node3D) -> void:
|
||||
world = w
|
||||
player = p
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
visible = false
|
||||
var dim := ColorRect.new()
|
||||
dim.color = Color(0, 0, 0, 0.6)
|
||||
dim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(dim)
|
||||
var centre := CenterContainer.new()
|
||||
centre.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
centre.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(centre)
|
||||
var box := VBoxContainer.new()
|
||||
box.add_theme_constant_override("separation", 6)
|
||||
centre.add_child(box)
|
||||
_title = UITheme.label("", 22)
|
||||
_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(_title)
|
||||
var row := HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 14)
|
||||
box.add_child(row)
|
||||
var frame := Control.new()
|
||||
frame.custom_minimum_size = Vector2(SIDE, SIDE)
|
||||
row.add_child(frame)
|
||||
_tex = TextureRect.new()
|
||||
_tex.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_tex.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_tex.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||
frame.add_child(_tex)
|
||||
_marks = Control.new()
|
||||
_marks.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_marks.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_marks.draw.connect(_draw_marks)
|
||||
frame.add_child(_marks)
|
||||
var legend := VBoxContainer.new()
|
||||
legend.add_theme_constant_override("separation", 4)
|
||||
row.add_child(legend)
|
||||
legend.add_child(UITheme.label("Key", 16, UITheme.TEXT_DIM))
|
||||
var you := UITheme.label("● You", 14)
|
||||
you.add_theme_color_override("font_color", Color(1, 1, 1))
|
||||
legend.add_child(you)
|
||||
for k in MARKS:
|
||||
var l := UITheme.label("■ %s" % MARKS[k][1], 14)
|
||||
l.add_theme_color_override("font_color", MARKS[k][0])
|
||||
legend.add_child(l)
|
||||
_facing = UITheme.label("", 15, UITheme.TEXT_DIM)
|
||||
_facing.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(_facing)
|
||||
var help := UITheme.label("N or Esc to close · north is up · the map fills in as you explore", 13, UITheme.TEXT_DIM)
|
||||
help.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(help)
|
||||
|
||||
func is_open() -> bool:
|
||||
return visible
|
||||
|
||||
func open() -> void:
|
||||
_tex.texture = ImageTexture.create_from_image(render(world))
|
||||
_title.text = "The %s" % preload("res://scripts/world/terrain.gd").worldlet_name(world.worldlet_id())
|
||||
visible = true
|
||||
player.ui_open = true
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
player.ui_open = false
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
func toggle() -> void:
|
||||
if visible:
|
||||
close()
|
||||
else:
|
||||
open()
|
||||
|
||||
func _process(_d: float) -> void:
|
||||
if not visible:
|
||||
return
|
||||
var h: float = Compass.heading(player)
|
||||
var names := ["north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west"]
|
||||
_facing.text = "Facing %s" % names[int(round(h / 45.0)) % 8]
|
||||
if world.underground_at(player.global_position):
|
||||
_facing.text += " · in the tunnel, under the cliff"
|
||||
_marks.queue_redraw()
|
||||
|
||||
## Where (x, z) in the world falls on the map, in its frame's pixels.
|
||||
func _to_map(p: Vector3) -> Vector2:
|
||||
var s: float = world.terrain.size
|
||||
return Vector2((p.x + s * 0.5) / s, (p.z + s * 0.5) / s) * SIDE
|
||||
|
||||
func _draw_marks() -> void:
|
||||
var font := get_theme_default_font()
|
||||
for m in world.map_marks():
|
||||
var at := _to_map(m.pos)
|
||||
var col: Color = MARKS.get(m.kind, [Color.WHITE])[0]
|
||||
var r := 3.0 if m.kind == "build" else 6.0
|
||||
_marks.draw_rect(Rect2(at - Vector2(r, r), Vector2(r, r) * 2.0), col)
|
||||
_marks.draw_rect(Rect2(at - Vector2(r, r), Vector2(r, r) * 2.0), Color(0, 0, 0, 0.7), false, 1.0)
|
||||
if m.kind in ["den", "barrow", "stones", "seam", "pier", "cave"]:
|
||||
_marks.draw_string(font, at + Vector2(9, 5), String(m.label), HORIZONTAL_ALIGNMENT_LEFT, -1, 13, col)
|
||||
# You: a dot (the way you're facing is on the compass).
|
||||
var p := player.global_position
|
||||
if world.underground_at(p) and world.tunnel != null:
|
||||
p = world.tunnel.seam_at # (down the tunnel: shown at its top)
|
||||
var me := _to_map(p)
|
||||
_marks.draw_circle(me, 6.5, Color(0, 0, 0, 0.8))
|
||||
_marks.draw_circle(me, 5.0, Color(1, 1, 1))
|
||||
# North.
|
||||
_marks.draw_string(font, Vector2(SIDE - 26, 24), "N", HORIZONTAL_ALIGNMENT_LEFT, -1, 18, Color(1.0, 0.55, 0.35))
|
||||
_marks.draw_line(Vector2(SIDE - 20, 30), Vector2(SIDE - 20, 48), Color(1.0, 0.55, 0.35), 2.0)
|
||||
|
||||
## The map image: the terrain, coloured and shaded, blank where unexplored.
|
||||
static func render(w: Node3D) -> Image:
|
||||
var t: Node3D = w.terrain
|
||||
var img := Image.create(RES, RES, false, Image.FORMAT_RGB8)
|
||||
var s: float = t.size
|
||||
var step := s / RES
|
||||
var blank := Color(0.16, 0.14, 0.12)
|
||||
for iy in range(RES):
|
||||
for ix in range(RES):
|
||||
var x := -s * 0.5 + (ix + 0.5) * step
|
||||
var z := -s * 0.5 + (iy + 0.5) * step
|
||||
if not w.seen(x, z):
|
||||
# (a faint paper grain, so blank reads as "not yet", not "nothing")
|
||||
img.set_pixel(ix, iy, blank.lightened(0.04 * float((ix * 7 + iy * 13) % 3) / 2.0))
|
||||
continue
|
||||
img.set_pixel(ix, iy, ground_colour(t, x, z))
|
||||
return img
|
||||
|
||||
static func ground_colour(t: Node3D, x: float, z: float) -> Color:
|
||||
var h: float = t.sample_height(x, z)
|
||||
var surface: float = t.water_level if t.inside_boundary(x, z, -2.0) else t.sea_level
|
||||
if h < surface:
|
||||
var deep := clampf((surface - h) / 6.0, 0.0, 1.0)
|
||||
return Color(0.30, 0.52, 0.62).lerp(Color(0.10, 0.22, 0.36), deep)
|
||||
var n: Vector3 = t.sample_normal(x, z)
|
||||
var c: Color
|
||||
if h > t.snow_height:
|
||||
c = Color(0.92, 0.93, 0.95)
|
||||
elif n.y < 0.75:
|
||||
c = Color(0.48, 0.46, 0.43) # rock
|
||||
elif h < surface + 0.9:
|
||||
c = Color(0.80, 0.74, 0.55) # shore
|
||||
else:
|
||||
c = Color(0.36, 0.52, 0.27).lerp(Color(0.46, 0.50, 0.32), clampf(h / 20.0, 0.0, 1.0))
|
||||
# Shading from the north-west, so hills and cliffs stand out.
|
||||
var light := clampf(0.75 + (-n.x - n.z) * 0.9, 0.45, 1.25)
|
||||
return Color(c.r * light, c.g * light, c.b * light)
|
||||
1
scripts/ui/map_screen.gd.uid
Normal file
1
scripts/ui/map_screen.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b80va7jn1pyxo
|
||||
@@ -15,6 +15,7 @@ const CONTROLS := [
|
||||
["E on a bed", "Set spawn point / sleep (night, under a roof)"],
|
||||
["1 - 8, wheel", "Hotbar (the bag row you're on)"], ["K / M", "Hotbar shows the bag row above / below"],
|
||||
["Q (shift+Q)", "Drop what you're holding (just one)"], ["Tab", "Inventory & crafting (shift-click Craft for 5)"],
|
||||
["N", "Map (fills in as you explore; the compass is along the top)"],
|
||||
["Shift + click", "Move stack to/from a chest"], ["Right click", "Split stack"],
|
||||
["Esc", "This menu (game autosaves every 2 min)"],
|
||||
]
|
||||
|
||||
@@ -163,7 +163,7 @@ func _on_new_day(day: int) -> void:
|
||||
if Weather.hard_winter(day + 1) and not Weather.hard_winter(day):
|
||||
Actions.tell_all("The air has a bite to it. A hard winter is coming tomorrow: three bitter days. Stock up on food and firewood.")
|
||||
elif Weather.hard_winter(day) and not Weather.hard_winter(day - 1):
|
||||
Actions.tell_all("A hard winter has set in. The lake's edge has frozen, and the animals are holed up.")
|
||||
Actions.tell_all("A hard winter has set in. The %s's edge has frozen, and the animals are holed up." % ("lake" if world.worldlet_id() == "valley" else "pond"))
|
||||
elif Weather.hard_winter(day - 1) and not Weather.hard_winter(day):
|
||||
Actions.tell_all("The hard winter is over.")
|
||||
|
||||
|
||||
@@ -566,6 +566,11 @@ func _process(delta: float) -> void:
|
||||
if _spoil_left <= 0.0:
|
||||
_spoil_left = SPOIL_EVERY
|
||||
spoil_all()
|
||||
if not server_mode:
|
||||
_explore_left -= delta
|
||||
if _explore_left <= 0.0:
|
||||
_explore_left = 0.5
|
||||
explore(player.global_position)
|
||||
if not Saves.has_world() or get_tree().paused:
|
||||
return
|
||||
_session_time += delta
|
||||
@@ -601,6 +606,7 @@ func save_now() -> bool:
|
||||
func _exit_tree() -> void:
|
||||
if server_mode and Saves.has_world() and _session_time > 0.0:
|
||||
save_now()
|
||||
save_online_map()
|
||||
if Guide.world == self:
|
||||
Guide.stop()
|
||||
if Hints.world == self:
|
||||
@@ -676,6 +682,7 @@ func collect_save() -> Dictionary:
|
||||
"barrow": barrow.save_data() if barrow != null else {},
|
||||
"stones": stones.save_data() if stones != null else {},
|
||||
"tunnel": tunnel.to_data() if tunnel != null else {},
|
||||
"map": map_data(),
|
||||
}
|
||||
|
||||
func _shared_state(harvested: Dictionary, regrow: Dictionary, taken: Array, dropped: Array, pieces: Array) -> Dictionary:
|
||||
@@ -823,6 +830,8 @@ func _apply_save(s: Dictionary, with_player: bool) -> void:
|
||||
stones.load_data(s.get("stones", {}))
|
||||
if tunnel != null:
|
||||
tunnel.load_data(s.get("tunnel", {}))
|
||||
if with_player:
|
||||
load_map_data(String(s.get("map", "")))
|
||||
# The bear (bear_range.gd): applied when it's set up, after loading.
|
||||
_saved_bear = s.get("bear", {})
|
||||
if bears != null:
|
||||
@@ -864,6 +873,107 @@ func _physics_process(delta: float) -> void:
|
||||
push_warning("[world] player left the map at %s; respawning" % p)
|
||||
_respawn()
|
||||
|
||||
# --- the map (Gitea issue #1) ---------------------------------------------------
|
||||
# What you've seen of this worldlet, as a grid of MAP_CELLS x MAP_CELLS cells
|
||||
# over the whole chunk, filled in around you as you go (scripts/ui/map_screen.gd
|
||||
# draws it). Saved with the worldlet in single-player; online, on your own
|
||||
# computer per server and worldlet (the server doesn't need to know).
|
||||
|
||||
const MAP_CELLS := 80
|
||||
const SEE_RADIUS := 30.0 ## how far around you the map fills in
|
||||
var explored := PackedByteArray()
|
||||
var _explore_left := 0.0
|
||||
|
||||
func _ensure_map() -> void:
|
||||
if explored.size() != MAP_CELLS * MAP_CELLS:
|
||||
explored = PackedByteArray()
|
||||
explored.resize(MAP_CELLS * MAP_CELLS)
|
||||
|
||||
## The map cell (x, z) falls in.
|
||||
func map_cell(x: float, z: float) -> Vector2i:
|
||||
var k: float = float(MAP_CELLS) / terrain.size
|
||||
return Vector2i(clampi(int((x + terrain.size * 0.5) * k), 0, MAP_CELLS - 1), clampi(int((z + terrain.size * 0.5) * k), 0, MAP_CELLS - 1))
|
||||
|
||||
func seen(x: float, z: float) -> bool:
|
||||
_ensure_map()
|
||||
var c := map_cell(x, z)
|
||||
return explored[c.y * MAP_CELLS + c.x] != 0
|
||||
|
||||
## Fills in the map around `p` (not from down the tunnel: nothing to see).
|
||||
func explore(p: Vector3) -> void:
|
||||
if underground_at(p) or terrain == null or not terrain.built:
|
||||
return
|
||||
_ensure_map()
|
||||
var cell: float = terrain.size / float(MAP_CELLS)
|
||||
var r := int(ceil(SEE_RADIUS / cell))
|
||||
var c := map_cell(p.x, p.z)
|
||||
for dz in range(-r, r + 1):
|
||||
for dx in range(-r, r + 1):
|
||||
var x := c.x + dx
|
||||
var z := c.y + dz
|
||||
if x < 0 or z < 0 or x >= MAP_CELLS or z >= MAP_CELLS:
|
||||
continue
|
||||
if Vector2(dx, dz).length() * cell <= SEE_RADIUS:
|
||||
explored[z * MAP_CELLS + x] = 1
|
||||
|
||||
func map_data() -> String:
|
||||
_ensure_map()
|
||||
return Marshalls.raw_to_base64(explored.compress(FileAccess.COMPRESSION_DEFLATE))
|
||||
|
||||
func load_map_data(s: String) -> void:
|
||||
explored = PackedByteArray()
|
||||
if not s.is_empty():
|
||||
var raw: PackedByteArray = Marshalls.base64_to_raw(s)
|
||||
if not raw.is_empty():
|
||||
explored = raw.decompress(MAP_CELLS * MAP_CELLS, FileAccess.COMPRESSION_DEFLATE)
|
||||
_ensure_map()
|
||||
|
||||
## Online: your map of this server's worldlet, kept on your computer.
|
||||
func _online_map_path() -> String:
|
||||
var key := "%s_%d_%s" % [String(Net.server_info.get("server_name", "server")), int(terrain.noise_seed), worldlet_id()]
|
||||
return "user://maps/%s.map" % key.validate_filename()
|
||||
|
||||
func save_online_map() -> void:
|
||||
if not is_online():
|
||||
return
|
||||
DirAccess.make_dir_recursive_absolute("user://maps")
|
||||
var f := FileAccess.open(_online_map_path(), FileAccess.WRITE)
|
||||
if f != null:
|
||||
f.store_string(map_data())
|
||||
|
||||
func load_online_map() -> void:
|
||||
load_map_data(FileAccess.get_file_as_string(_online_map_path()) if FileAccess.file_exists(_online_map_path()) else "")
|
||||
|
||||
## What the map marks, where you've seen it: [{kind, pos (Vector3), label}].
|
||||
func map_marks() -> Array:
|
||||
var out: Array = []
|
||||
var add := func(kind: String, pos: Vector3, label: String):
|
||||
if seen(pos.x, pos.z):
|
||||
out.append({"kind": kind, "pos": pos, "label": label})
|
||||
if den != null and den.found:
|
||||
add.call("den", den.global_position, "Wolf den")
|
||||
if barrow != null and barrow.found:
|
||||
add.call("barrow", barrow.global_position, "The barrow")
|
||||
if stones != null and stones.found:
|
||||
add.call("stones", stones.global_position, "Standing stones")
|
||||
if tunnel != null:
|
||||
add.call("seam", tunnel.seam_at, "The tunnel" if tunnel.opened else "Cracked seam")
|
||||
if pier != null:
|
||||
add.call("pier", (pier.start + pier.end) * 0.5, "The pier")
|
||||
if sea_cave != null:
|
||||
add.call("cave", sea_cave.global_position, "Sea cave")
|
||||
for piece in all_pieces():
|
||||
var id: String = String(piece.get("item_id"))
|
||||
if piece.is_in_group("bed"):
|
||||
add.call("bed", piece.global_position, ItemDB.item_name(id))
|
||||
elif BuildPiece.is_build(ItemDB.get_item(id)):
|
||||
add.call("build", piece.global_position, "")
|
||||
elif id in ["campfire", "chest", "workbench", "forge", "hearth"]:
|
||||
add.call("camp", piece.global_position, ItemDB.item_name(id))
|
||||
elif id == "grave":
|
||||
add.call("grave", piece.global_position, "Your things")
|
||||
return out
|
||||
|
||||
## In the cliff tunnel (J1): no daylight, no weather, sheltered.
|
||||
func underground_at(p: Vector3) -> bool:
|
||||
return tunnel != null and tunnel.contains(p)
|
||||
@@ -1086,6 +1196,8 @@ func _go_online() -> void:
|
||||
net_sync.name = "NetSync"
|
||||
add_child(net_sync)
|
||||
net_sync.setup(self)
|
||||
load_online_map() # (your map of this server, from your own computer)
|
||||
Net.left.connect(func(_why = ""): save_online_map(), CONNECT_ONE_SHOT)
|
||||
Net.left.connect(_on_net_left)
|
||||
get_tree().auto_accept_quit = false # leave the server cleanly on window close
|
||||
# The island's changes come from the server from now on.
|
||||
|
||||
Reference in New Issue
Block a user