188 lines
8.2 KiB
GDScript
188 lines
8.2 KiB
GDScript
extends Node
|
|
const EVENTS := preload("res://scripts/planets/surface_events.gd")
|
|
const GENERATOR := preload("res://scripts/planets/surface_generator.gd")
|
|
const SCENERY := preload("res://scripts/planets/surface_scenery.gd")
|
|
const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
|
|
var captain: Resource
|
|
var state: Resource
|
|
var profile: Dictionary
|
|
|
|
class InventoryStub extends Node:
|
|
signal ammunition_changed
|
|
signal inventory_changed
|
|
var missile_ammunition := 2
|
|
func add_item(_item: Resource) -> bool:
|
|
return false
|
|
|
|
func fresh(kind: String, seed_value := 123) -> Dictionary:
|
|
captain = preload("res://scripts/captain/captain_progress.gd").new()
|
|
captain.credits = 100000
|
|
captain.parts = 100
|
|
for key in ["iron","copper","nickel","silicon","tungsten","mercury","sulfur","uranium"]:
|
|
captain.minerals[key] = 100
|
|
state = captain.expedition
|
|
assert(state.begin_visit(profile))
|
|
var event := {"kind":kind,"seed":seed_value,"complete":false}
|
|
EVENTS.prepare(event,captain,state)
|
|
return event
|
|
|
|
func _ready() -> void:
|
|
GameState.system_level = 3
|
|
profile = PROFILES.generate(777,1,"Test wydarzeń")
|
|
profile.difficulty = 0.0
|
|
profile.oxygen = 95
|
|
profile.temperature = 20
|
|
profile.flora = 0
|
|
for kind in EVENTS.KINDS:
|
|
var event := fresh(kind)
|
|
assert(not str(EVENTS.STORIES[kind]).is_empty())
|
|
var offers := EVENTS.options(event,captain,state)
|
|
assert(not offers.is_empty())
|
|
var snapshot := var_to_str(offers)
|
|
assert(snapshot == var_to_str(EVENTS.options(event,captain,state)))
|
|
event.rolls = [0.0,0.0,0.0]
|
|
var result := EVENTS.resolve(event,str(offers[0].id),captain,state)
|
|
assert(result.success)
|
|
if kind == "scan_cache":
|
|
assert(event.scanned and not event.complete)
|
|
var before: int = captain.credits
|
|
assert(not EVENTS.resolve(event,"scan",captain,state).success)
|
|
assert(captain.credits == before)
|
|
assert(EVENTS.resolve(event,"open",captain,state).success)
|
|
if kind == "evacuation":
|
|
assert(event.stage == 1 and not event.complete)
|
|
assert(EVENTS.resolve(event,"help",captain,state).success)
|
|
assert(EVENTS.resolve(event,"help",captain,state).success)
|
|
assert(event.complete)
|
|
var credits_before: int = captain.credits
|
|
var cargo_before := var_to_str([state.cargo,state.cargo_credits,state.cargo_parts,state.cargo_missiles,state.cargo_modules])
|
|
assert(not EVENTS.resolve(event,str(offers[0].id),captain,state).success)
|
|
assert(captain.credits == credits_before)
|
|
assert(cargo_before == var_to_str([state.cargo,state.cargo_credits,state.cargo_parts,state.cargo_missiles,state.cargo_modules]))
|
|
# Server-side affordability and action validation; no partial payment or reroll.
|
|
var event := fresh("blind_cache")
|
|
captain.credits = 0
|
|
var prize_before := var_to_str(event.prize)
|
|
assert(not EVENTS.resolve(event,"open",captain,state).success)
|
|
assert(not event.complete and captain.credits == 0)
|
|
assert(prize_before == var_to_str(event.prize))
|
|
assert(not EVENTS.resolve(event,"forged",captain,state).success)
|
|
event = fresh("rescue")
|
|
captain.parts = 4
|
|
state.cargo_parts = 1
|
|
assert(not EVENTS.resolve(event,"rescue",captain,state).success)
|
|
assert(captain.parts == 4 and state.cargo_parts == 1 and not event.complete)
|
|
state.cargo_parts = 3
|
|
event.rolls = [1.0,1.0,1.0]
|
|
var hp_before: float = state.lander_hp
|
|
var result := EVENTS.resolve(event,"rescue",captain,state)
|
|
assert(not result.success and event.complete and state.lander_hp < hp_before)
|
|
assert(captain.parts == 1 and state.cargo_parts == 0)
|
|
# A prototype pays the base even when the test fails.
|
|
event = fresh("prototype")
|
|
event.rolls = [1.0,1.0,1.0]
|
|
assert(not EVENTS.resolve(event,"test",captain,state).success)
|
|
assert(state.cargo_credits == 35*GameState.system_level and state.cargo_modules.is_empty())
|
|
# Later failed rescue stages preserve earlier rewards; stop is terminal.
|
|
event = fresh("evacuation")
|
|
event.rolls = [0.0,1.0,1.0]
|
|
assert(EVENTS.resolve(event,"help",captain,state).success)
|
|
var first_payment: int = state.cargo_credits
|
|
assert(not EVENTS.resolve(event,"help",captain,state).success)
|
|
assert(event.complete and state.cargo_credits == first_payment)
|
|
event = fresh("evacuation")
|
|
event.rolls = [0.0,0.0,0.0]
|
|
EVENTS.resolve(event,"help",captain,state)
|
|
first_payment = state.cargo_credits
|
|
assert(EVENTS.resolve(event,"stop",captain,state).success)
|
|
assert(event.complete and state.cargo_credits == first_payment)
|
|
# Repair clamps correctly and is unavailable at full HP.
|
|
event = fresh("service")
|
|
state.lander_hp = 97
|
|
assert(EVENTS.resolve(event,"repair",captain,state).success)
|
|
assert(state.lander_hp == 100)
|
|
event = fresh("service")
|
|
state.lander_hp = 100
|
|
assert(EVENTS.options(event,captain,state).is_empty())
|
|
assert(not EVENTS.resolve(event,"repair",captain,state).success)
|
|
# Purchased rockets stay temporary, use the shop discount, and transfer once.
|
|
event = fresh("trader")
|
|
assert(event.missile_count >= 3 and event.missile_count <= 6)
|
|
assert(event.missile_price < event.missile_normal_price)
|
|
var expected: int = event.missile_count
|
|
var inventory := InventoryStub.new()
|
|
assert(EVENTS.resolve(event,"missiles",captain,state).success)
|
|
assert(state.cargo_missiles == expected and inventory.missile_ammunition == 2)
|
|
state.finish_visit(captain,true)
|
|
state.deliver_modules(inventory)
|
|
assert(inventory.missile_ammunition == 2+expected)
|
|
state.deliver_modules(inventory)
|
|
assert(inventory.missile_ammunition == 2+expected)
|
|
# Already-owned goods survive destruction; all new cargo is lost.
|
|
event = fresh("trader")
|
|
EVENTS.resolve(event,"missiles",captain,state)
|
|
state.cargo_credits = 123
|
|
state.cargo_parts = 7
|
|
state.cargo_modules.append(event.module)
|
|
var committed_credits: int = captain.credits
|
|
state.damage(100)
|
|
assert(not state.finish_visit(captain,true).success)
|
|
assert(captain.credits == committed_credits and captain.parts == 100)
|
|
assert(state.delivered_missiles == 0 and state.delivered_modules.is_empty())
|
|
assert(state.cargo_credits == 0 and state.cargo_parts == 0 and state.cargo_missiles == 0)
|
|
# Successful payout is idempotent and never stores parts as minerals.
|
|
event = fresh("safe_bet")
|
|
EVENTS.resolve(event,"safe",captain,state)
|
|
state.cargo_credits = 123
|
|
committed_credits = captain.credits
|
|
state.finish_visit(captain,true)
|
|
state.finish_visit(captain,true)
|
|
assert(captain.parts == 106 and captain.credits == committed_credits+123)
|
|
assert(not captain.minerals.has("parts") and not captain.minerals.has("missile"))
|
|
inventory.free()
|
|
# Event modules use normal rarity rolls and a strictly decreasing biome level tail.
|
|
var bonus_rng := RandomNumberGenerator.new()
|
|
bonus_rng.seed = 727373
|
|
var histogram := PackedInt32Array()
|
|
histogram.resize(11)
|
|
for index in 100000:
|
|
var bonus := EVENTS.roll_module_bonus(bonus_rng,{"difficulty":1.0})
|
|
assert(bonus >= 2 and bonus <= 10)
|
|
histogram[bonus] += 1
|
|
for index in range(2,10):
|
|
assert(histogram[index] > histogram[index+1])
|
|
assert(histogram[0] == 0 and histogram[1] == 0)
|
|
assert(histogram[2] > 34000 and histogram[2] < 37000)
|
|
assert(histogram[10] > 950 and histogram[10] < 1350)
|
|
var rarities := {}
|
|
for index in 1000:
|
|
var item := EVENTS._module(bonus_rng,32,{"difficulty":1.0})
|
|
assert(item.item_level >= 34 and item.item_level <= 42)
|
|
assert(item.rarity >= 0 and item.rarity <= 4)
|
|
rarities[item.rarity] = true
|
|
assert(rarities.size() == 5)
|
|
assert(EVENTS._module(bonus_rng,1000,{"difficulty":1.0}).item_level == 1000)
|
|
print("MODULE LOOT PASS: normal through legendary rarity, hard biome level +2..+10, decreasing exponential weights; histogram=",histogram)
|
|
# 50% roll, one marker maximum, all ten kinds on usable terrain.
|
|
var data := GENERATOR.generate(200,profile)
|
|
data.trees = []
|
|
data.rocks = []
|
|
data.mountains = []
|
|
data.pois = []
|
|
data.heights.fill(0.5)
|
|
var counts := {}
|
|
var encounters := 0
|
|
var rng := RandomNumberGenerator.new()
|
|
for index in 1000:
|
|
data.seed = index
|
|
rng.seed = index
|
|
SCENERY.generate(data,rng)
|
|
assert(data.events.size() <= 1)
|
|
encounters += data.events.size()
|
|
for spawned in data.events:
|
|
assert(EVENTS.KINDS.has(spawned.kind))
|
|
counts[spawned.kind] = int(counts.get(spawned.kind,0))+1
|
|
assert(encounters > 450 and encounters < 550 and counts.size() == 10)
|
|
print("SURFACE EVENTS PASS: all 10 stories/actions, fixed offers, costs, discounts, scan stages, success/failure, evacuation, repair, safe return, destruction, one-time delivery. Spawn count / 1000: ",encounters,"; kinds: ",counts)
|
|
get_tree().quit()
|