Add planet scanner and octopus biome
This commit is contained in:
parent
2ecd61de98
commit
8fbcc340ce
21 changed files with 784 additions and 30 deletions
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=46 format=3]
|
||||
[gd_scene load_steps=48 format=3]
|
||||
[ext_resource type="PackedScene" path="res://ui/menus/BuildMenu.tscn" id="menu_build"]
|
||||
[ext_resource type="PackedScene" path="res://ui/menus/HangarMenu.tscn" id="menu_hangar"]
|
||||
[ext_resource type="PackedScene" path="res://ui/menus/LabMenu.tscn" id="menu_lab"]
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
[ext_resource type="PackedScene" path="res://ui/menus/GarrisonMenu.tscn" id="menu_garrison"]
|
||||
[ext_resource type="PackedScene" path="res://ui/menus/SkillMenu.tscn" id="menu_skill"]
|
||||
[ext_resource type="PackedScene" path="res://ui/menus/DevMenu.tscn" id="menu_dev"]
|
||||
[ext_resource type="PackedScene" path="res://ui/menus/ScannerMenu.tscn" id="menu_scanner"]
|
||||
|
||||
|
||||
[ext_resource type="Script" path="res://world/Starfield.gd" id="1"]
|
||||
|
|
@ -45,6 +46,7 @@
|
|||
[ext_resource type="Script" path="res://ui/SpeedFader.gd" id="31"]
|
||||
[ext_resource type="Script" path="res://world/QuestDirector.gd" id="quest_dir"]
|
||||
[ext_resource type="Script" path="res://ui/QuestPanel.gd" id="quest_panel"]
|
||||
[ext_resource type="Script" path="res://world/OctopusField.gd" id="octopus_field"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="1"]
|
||||
shader = ExtResource("5")
|
||||
|
|
@ -104,6 +106,9 @@ script = ExtResource("6")
|
|||
[node name="AsteroidField" type="Node2D" parent="."]
|
||||
script = ExtResource("7")
|
||||
|
||||
[node name="OctopusField" type="Node2D" parent="."]
|
||||
script = ExtResource("octopus_field")
|
||||
|
||||
[node name="PlanetField" type="Node2D" parent="."]
|
||||
script = ExtResource("11")
|
||||
|
||||
|
|
@ -513,6 +518,7 @@ icon = 1
|
|||
[node name="GarrisonMenu" parent="UI" instance=ExtResource("menu_garrison")]
|
||||
[node name="SkillMenu" parent="UI" instance=ExtResource("menu_skill")]
|
||||
[node name="DevMenu" parent="UI" instance=ExtResource("menu_dev")]
|
||||
[node name="ScannerMenu" parent="UI" instance=ExtResource("menu_scanner")]
|
||||
[node name="QuestPanel" type="Control" parent="UI" groups=["modal_menus"]]
|
||||
visible = false
|
||||
mouse_filter = 2
|
||||
|
|
|
|||
62
world/OctopusField.gd
Normal file
62
world/OctopusField.gd
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
extends Node2D
|
||||
|
||||
# A bounded ambient threat attached to the OCTOPUS biome. Like AsteroidField,
|
||||
# this node owns only the nearby presentation: the biome itself and its one
|
||||
# boss planet remain deterministic WorldGen data.
|
||||
|
||||
@export var spawn_interval: float = 2.4
|
||||
@export var max_octopuses: int = 7
|
||||
@export var spawn_margin: float = 180.0
|
||||
@export var retire_distance: float = 2600.0
|
||||
|
||||
@onready var fleet: Node2D = get_node("../Fleet")
|
||||
|
||||
var octopus_scene: PackedScene = preload("res://boss/BossMinion.tscn")
|
||||
var active: Array = []
|
||||
var spawn_left: float = 0.0
|
||||
var spawn_count: int = 0
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
active = active.filter(func(o): return is_instance_valid(o))
|
||||
var in_biome: bool = WorldGen.biome_at_chunk(
|
||||
WorldGen.chunk_coords(fleet.get_centroid())) == WorldGen.Biome.OCTOPUS
|
||||
if not in_biome or GameState.intro_active:
|
||||
retire_all()
|
||||
spawn_left = 0.0
|
||||
return
|
||||
for octopus in active.duplicate():
|
||||
if octopus.global_position.distance_to(fleet.get_centroid()) > retire_distance:
|
||||
octopus.retire()
|
||||
active.erase(octopus)
|
||||
spawn_left -= delta
|
||||
if spawn_left <= 0.0 and active.size() < max_octopuses:
|
||||
spawn_one()
|
||||
spawn_left = spawn_interval
|
||||
|
||||
|
||||
func spawn_one() -> Node2D:
|
||||
if fleet == null or fleet.ships.is_empty():
|
||||
return null
|
||||
var visible_rect: Rect2 = ViewUtils.get_visible_world_rect(self)
|
||||
var radius: float = maxf(visible_rect.size.length() * 0.5 + spawn_margin,
|
||||
620.0)
|
||||
var angle: float = randf() * TAU
|
||||
var octopus: Node2D = octopus_scene.instantiate()
|
||||
# Ambient hatchlings are sturdy enough to harass a travelling fleet, but
|
||||
# never marked: only the boss encounter's golden hatchlings open eyes.
|
||||
octopus.max_hp = 7
|
||||
octopus.position = fleet.get_centroid() + Vector2.from_angle(angle) * radius
|
||||
get_parent().add_child(octopus)
|
||||
octopus.uses_dash_pattern = spawn_count % 3 == 2
|
||||
octopus.add_to_group("roaming_octopuses")
|
||||
active.append(octopus)
|
||||
spawn_count += 1
|
||||
return octopus
|
||||
|
||||
|
||||
func retire_all() -> void:
|
||||
for octopus in active:
|
||||
if is_instance_valid(octopus):
|
||||
octopus.retire()
|
||||
active.clear()
|
||||
|
|
@ -15,6 +15,8 @@ extends ColorRect
|
|||
|
||||
@onready var camera: Camera2D = get_node("../../Camera2D")
|
||||
|
||||
var current_chunk: Vector2i = Vector2i(2147483647, 2147483647)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
var viewport_size: Vector2 = get_viewport_rect().size
|
||||
|
|
@ -26,3 +28,8 @@ func _process(_delta: float) -> void:
|
|||
material.set_shader_parameter("camera_frac", p - cell)
|
||||
material.set_shader_parameter("camera_zoom", 1.0 / camera.zoom.x)
|
||||
material.set_shader_parameter("viewport_size", viewport_size)
|
||||
var chunk: Vector2i = WorldGen.chunk_coords(camera.global_position)
|
||||
if chunk != current_chunk:
|
||||
current_chunk = chunk
|
||||
material.set_shader_parameter("bg_color",
|
||||
WorldGen.biome_background_color(WorldGen.biome_at_chunk(chunk)))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const PLANET_DEFINITIONS = preload("res://planets/PlanetDefinitions.gd")
|
|||
# revisiting the same chunk always yields the same biome, and biomes form
|
||||
# smooth multi-chunk regions instead of a per-chunk random checkerboard.
|
||||
|
||||
enum Biome { SPACE, ASTEROID_FIELD }
|
||||
enum Biome { SPACE, ASTEROID_FIELD, OCTOPUS }
|
||||
|
||||
const CHUNK_SIZE: float = 1200.0
|
||||
const NOISE_SEED: int = 1337
|
||||
|
|
@ -37,10 +37,18 @@ const BLACK_HOLE_CHANCE: float = 0.05
|
|||
# from 0.08 after playtesting found that rate too scarce for how central
|
||||
# research points had become to progression.
|
||||
const RESEARCH_LAB_CHANCE: float = 0.16
|
||||
# A boss world is rarer than every ordinary discovery. Its roll uses a
|
||||
# separately seeded generator below, so adding it cannot rename or re-roll
|
||||
# any planet in an existing world.
|
||||
const BOSS_PLANET_CHANCE: float = 0.025
|
||||
# The hostile region is authored into procedural space as one finite,
|
||||
# deterministic island. A probability per chunk can only say "rare"; over an
|
||||
# infinite world it inevitably creates infinitely many boss worlds. One
|
||||
# anchor and one shape make the actual rule representable: one multi-chunk
|
||||
# octopus biome, with its boss at the centre.
|
||||
const OCTOPUS_BIOME_CENTER: Vector2i = Vector2i(12, -9)
|
||||
const OCTOPUS_BIOME_OFFSETS: Array[Vector2i] = [
|
||||
Vector2i(0, 0), Vector2i(-1, 0), Vector2i(1, 0),
|
||||
Vector2i(0, -1), Vector2i(0, 1),
|
||||
Vector2i(-1, -1), Vector2i(1, -1),
|
||||
Vector2i(-1, 1), Vector2i(1, 1),
|
||||
]
|
||||
|
||||
# DEV MODE ONLY: one of every planet type, parked in the chunks immediately
|
||||
# around the spawn so all five can be reached in seconds.
|
||||
|
|
@ -113,6 +121,8 @@ static func chunk_origin(chunk: Vector2i) -> Vector2:
|
|||
|
||||
|
||||
static func biome_at_chunk(chunk: Vector2i) -> int:
|
||||
if is_octopus_chunk(chunk):
|
||||
return Biome.OCTOPUS
|
||||
var n: float = _get_noise().get_noise_2d(float(chunk.x), float(chunk.y))
|
||||
if n > ASTEROID_FIELD_THRESHOLD:
|
||||
return Biome.ASTEROID_FIELD
|
||||
|
|
@ -122,6 +132,10 @@ static func biome_at_chunk(chunk: Vector2i) -> int:
|
|||
static func has_planet_at_chunk(chunk: Vector2i) -> bool:
|
||||
if _dev_planets_active() and DEV_PLANET_CHUNKS.has(chunk):
|
||||
return true
|
||||
if chunk == OCTOPUS_BIOME_CENTER:
|
||||
return true
|
||||
if is_octopus_chunk(chunk):
|
||||
return false
|
||||
var n: float = _get_planet_noise().get_noise_2d(float(chunk.x), float(chunk.y))
|
||||
return (biome_at_chunk(chunk) == Biome.ASTEROID_FIELD and n > PLANET_THRESHOLD) or (biome_at_chunk(chunk) == Biome.SPACE and n > GAS_GIANT_THRESHOLD)
|
||||
|
||||
|
|
@ -148,17 +162,12 @@ static func planet_data_at_chunk(chunk: Vector2i) -> Dictionary:
|
|||
var black_hole_roll: float = rng.randf()
|
||||
# Same principle, its own independent roll (#63).
|
||||
var research_lab_roll: float = rng.randf()
|
||||
# Independent from the established generation stream: existing chunks keep
|
||||
# their old names and shapes when this new rare outcome is introduced.
|
||||
var boss_rng := RandomNumberGenerator.new()
|
||||
boss_rng.seed = hash("boss:%d:%d" % [chunk.x, chunk.y])
|
||||
var boss_roll: float = boss_rng.randf()
|
||||
# A stable per-chunk roll makes gas giants persistent landmarks rather
|
||||
# than a different resource whenever the scene reloads.
|
||||
var gas_giant: bool = biome_at_chunk(chunk) == Biome.SPACE
|
||||
var definition_id: String = PLANET_DEFINITIONS.GAS_GIANT_ID if gas_giant else PLANET_DEFINITIONS.ROCKY_ID
|
||||
# Rare authored outcomes, seeded deterministically like everything else.
|
||||
if boss_roll < BOSS_PLANET_CHANCE:
|
||||
if chunk == OCTOPUS_BIOME_CENTER:
|
||||
definition_id = PLANET_DEFINITIONS.BOSS_ID
|
||||
elif black_hole_roll < BLACK_HOLE_CHANCE:
|
||||
definition_id = PLANET_DEFINITIONS.BLACK_HOLE_ID
|
||||
|
|
@ -186,5 +195,26 @@ static func biome_color(biome: int) -> Color:
|
|||
match biome:
|
||||
Biome.ASTEROID_FIELD:
|
||||
return Color(0.55, 0.36, 0.18, 1.0)
|
||||
Biome.OCTOPUS:
|
||||
return Color(0.34, 0.07, 0.24, 1.0)
|
||||
_:
|
||||
return Color(0.10, 0.13, 0.28, 1.0)
|
||||
|
||||
|
||||
static func biome_background_color(biome: int) -> Color:
|
||||
match biome:
|
||||
Biome.OCTOPUS:
|
||||
return Color(0.075, 0.012, 0.065, 1.0)
|
||||
_:
|
||||
return Color(0.02, 0.02, 0.08, 1.0)
|
||||
|
||||
|
||||
static func is_octopus_chunk(chunk: Vector2i) -> bool:
|
||||
return chunk - OCTOPUS_BIOME_CENTER in OCTOPUS_BIOME_OFFSETS
|
||||
|
||||
|
||||
static func octopus_biome_chunks() -> Array[Vector2i]:
|
||||
var chunks: Array[Vector2i] = []
|
||||
for offset in OCTOPUS_BIOME_OFFSETS:
|
||||
chunks.append(OCTOPUS_BIOME_CENTER + offset)
|
||||
return chunks
|
||||
|
|
|
|||
|
|
@ -58,7 +58,11 @@ func close_map() -> void:
|
|||
|
||||
|
||||
func recenter_view() -> void:
|
||||
var fleet_local: Vector2 = fleet.get_centroid() * content.WORLD_TO_LOCAL
|
||||
focus_world_position(fleet.get_centroid())
|
||||
|
||||
|
||||
func focus_world_position(world_position: Vector2) -> void:
|
||||
var fleet_local: Vector2 = world_position * content.WORLD_TO_LOCAL
|
||||
var viewport_center: Vector2 = get_viewport_rect().size / 2.0
|
||||
content.position = viewport_center - fleet_local * content.scale.x
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue