169 lines
8.9 KiB
GDScript
169 lines
8.9 KiB
GDScript
extends Node2D
|
|
const GEN := preload("res://scripts/items/item_generator.gd")
|
|
const DB := preload("res://resources/items/affix_database.tres")
|
|
const CATALOG := preload("res://scripts/game/ship_catalog.gd")
|
|
var player: CharacterBody2D
|
|
var inventory: ShipInventory
|
|
var report := {}
|
|
|
|
func _ready() -> void:
|
|
GameState.start_new_run()
|
|
GameState.system_level = 120
|
|
seed(1202026)
|
|
player = preload("res://scenes/game/player_ship.tscn").instantiate()
|
|
add_child(player)
|
|
player.set_physics_process(false)
|
|
inventory = player.get_node("ShipInventory")
|
|
player.get_node("WeaponController").set_physics_process(false)
|
|
inventory.unlocked_weapon_slots = 3
|
|
for tier in [0,2]:
|
|
var samples: Array[Dictionary] = []
|
|
for sample in 500:
|
|
loadout(tier)
|
|
samples.append(metrics())
|
|
var summary := {}
|
|
for key in samples[0]:
|
|
var values: Array = samples.map(func(row): return row[key])
|
|
values.sort()
|
|
summary[key] = {"min":values[0],"median":values[250],"p90":values[450],"max":values[499]}
|
|
summary["single_gun_sustainable_fraction"] = float(samples.filter(func(row): return row.regen >= row.single_gun_cost_s).size())/samples.size()
|
|
summary["two_guns_sustainable_fraction"] = float(samples.filter(func(row): return row.regen >= row.two_guns_cost_s).size())/samples.size()
|
|
report["random_boss" if tier==2 else "random_normal"] = summary
|
|
for focus in ["speed","energy","shield"]:
|
|
loadout(2,focus)
|
|
report[focus+"_max"] = metrics()
|
|
if focus == "energy":
|
|
report["energy_runtime"] = energy_simulation(30.0)
|
|
if focus == "speed":
|
|
report["movement_runtime"] = movement_simulation()
|
|
if focus == "shield":
|
|
report["shield_runtime"] = shield_simulation()
|
|
player.shields = 0.0
|
|
player._refill_recovery_budgets(1.0)
|
|
player.restore_shields_from_damage(1000.0)
|
|
report["shield_leech_actual_from_1000_damage"] = player.shields
|
|
loadout(0)
|
|
report["representative"] = metrics()
|
|
report["representative_energy_runtime"] = energy_simulation(30.0)
|
|
report["representative_single_gun_runtime"] = energy_simulation(30.0,1)
|
|
var star := Node2D.new()
|
|
add_child(star)
|
|
star.position = player.position
|
|
player.charging_star = star
|
|
report["solar_regen"] = player.get_energy_regeneration()
|
|
report["solar_two_guns_runtime"] = energy_simulation(30.0)
|
|
player.charging_star = null
|
|
var shields: ItemInstance = inventory.module_slots[0]
|
|
shields.special_orbs.append("polarization")
|
|
report["polarization"] = {"per_hit":player.max_shields*0.05,"energy_per_s_at_5_hits":player.max_shields*0.05*5,"generator_capacity":player.max_energy}
|
|
player.energy = 0.0
|
|
player.shields = player.max_shields
|
|
var before: float = player.shields
|
|
for hit in 5: player.take_damage(1.0)
|
|
report["polarization_actual_5_tiny_hits"] = {"shield_damage":before-player.shields,"energy_gained":player.energy}
|
|
var engine: ItemInstance = inventory.module_slots[2]
|
|
engine.special_orbs.append("atomic_engine")
|
|
report["atomic_engine_cost_s"] = player.get_mini_boost_energy_cost()
|
|
engine.special_orbs.append("booster")
|
|
report["booster_cost_s"] = player.get_mini_boost_energy_cost()
|
|
var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate()
|
|
add_child(enemy)
|
|
enemy.set_physics_process(false)
|
|
var enemy_metrics := []
|
|
for tier in [0,1,2]:
|
|
enemy.apply_level_settings(120,tier)
|
|
enemy_metrics.append({"tier":tier,"hull":enemy.max_hull,"shields":enemy.max_shields,"dps":enemy.get_weapon_dps(),"speed":enemy.approach_speed})
|
|
report["enemies"] = enemy_metrics
|
|
var laser_results: Array[Dictionary] = []
|
|
for sample in 200:
|
|
loadout(0)
|
|
for slot in 2:
|
|
inventory.weapon_slots[slot] = GEN.generate_for_definition(preload("res://resources/items/beam_laser.tres"),120)
|
|
inventory._recalculate_ship_stats()
|
|
laser_results.append(metrics())
|
|
var laser_costs: Array = laser_results.map(func(row): return row.two_guns_cost_s)
|
|
laser_costs.sort()
|
|
report["lasers"] = {"samples":200,"median_two_lasers_cost_s":laser_costs[100],"two_lasers_sustainable_fraction":float(laser_results.filter(func(row): return row.regen >= row.two_guns_cost_s).size())/200.0}
|
|
var file := FileAccess.open("res://artifacts/ship_level_120_audit.json",FileAccess.WRITE)
|
|
file.store_string(JSON.stringify(report,"\t"))
|
|
file.close()
|
|
print("SHIP_120_AUDIT_DONE: "+JSON.stringify(report))
|
|
|
|
func loadout(tier: int, focus := "") -> void:
|
|
player.base_max_speed = 108.0
|
|
player.base_max_hull = 100.0
|
|
player.base_max_shields = 100.0
|
|
player.base_max_energy = 100.0
|
|
var definitions := ["plasma_cannon","plasma_cannon","missile_launcher","reinforced_shield","hull_plating","engine_extension","compact_generator","ship_computer"]
|
|
for index in definitions.size():
|
|
var definition := load("res://resources/items/%s.tres" % definitions[index]) as ItemDefinition
|
|
var item := GEN.generate_for_definition(definition,120,tier)
|
|
if not focus.is_empty():
|
|
var factor := 9.33
|
|
item.primary_value_override = (5.0 if definition.primary_stat=="max_speed" else definition.primary_value*1.2)*factor
|
|
item.energy_regeneration_override = definition.energy_regeneration_bonus*1.2*factor
|
|
if definition.category == ItemDefinition.Category.WEAPON:
|
|
item.damage_min = roundf(definition.damage_lower_roll_max*factor)
|
|
item.damage_max = roundf((definition.damage_lower_roll_max+definition.damage_spread_roll_max)*factor)
|
|
var keys: Array = {"speed":["speed_percent","damage_percent","critical_chance_percent","critical_damage_percent","attack_speed_percent"],"energy":["energy_regeneration","attack_speed_percent","damage_percent","shield_percent","shield_regeneration"],"shield":["shield_percent","shield_regeneration","shield_on_damage_percent","damage_percent","critical_chance_percent"]}[focus]
|
|
item.additional_modifiers.clear()
|
|
for key in keys:
|
|
for affix in DB.affixes:
|
|
if affix.stat_key==key and affix.can_roll(definition,120):
|
|
var addon := ItemStatModifier.new()
|
|
addon.affix = affix
|
|
addon.stat_key = key
|
|
addon.value = affix.get_value_range(120).y
|
|
item.additional_modifiers.append(addon)
|
|
if index<3: inventory.weapon_slots[index]=item
|
|
else: inventory.module_slots[index-3]=item
|
|
inventory._recalculate_ship_stats()
|
|
player.energy = player.max_energy
|
|
player.shields = player.max_shields
|
|
player.hull = player.max_hull
|
|
player.is_docked = false
|
|
|
|
func metrics() -> Dictionary:
|
|
var first: ItemInstance = inventory.weapon_slots[0]
|
|
var second: ItemInstance = inventory.weapon_slots[1]
|
|
var cost := first.get_energy_cost()/first.get_fire_interval()
|
|
var two := cost+second.get_energy_cost()/second.get_fire_interval()
|
|
return {"speed":player.max_speed,"tab_speed":player.max_speed*2,"afterburner_speed":minf(600.0,player.max_speed*3),"max_energy":player.max_energy,"regen":player.get_energy_regeneration(),"single_gun_cost_s":cost,"two_guns_cost_s":two,"two_guns_and_tab_cost_s":two+player.get_mini_boost_energy_cost(),"shields":player.max_shields,"shield_regen":player.shield_regeneration,"shield_refill_s":player.max_shields/maxf(0.001,player.shield_regeneration),"shield_leech_percent":player.shield_on_damage_percent,"raw_dps":inventory.get_total_dps(),"shield_leech_s_at_50pct_accuracy_20pct_absorption":minf(player.max_shields*0.05,inventory.get_total_dps()*0.5*0.8*player.shield_on_damage_percent/100.0),"scanner_range":player.scanner_range,"stop_from_tab_s":player.max_speed*2/player.space_drag}
|
|
|
|
func energy_simulation(seconds: float, guns := 2) -> Dictionary:
|
|
var timers := [0.0,0.0]
|
|
var fired := 0
|
|
var failed := 0
|
|
var start: float = player.max_energy
|
|
player.energy = start
|
|
var controller: Node = player.get_node("WeaponController")
|
|
for frame in int(seconds*60):
|
|
player.energy = minf(player.max_energy,player.energy+player.get_energy_regeneration()/60.0)
|
|
for index in guns:
|
|
timers[index] -= 1.0/60.0
|
|
if timers[index]<=0:
|
|
var item: ItemInstance = inventory.weapon_slots[index]
|
|
if controller._consume_shot_resource(item): fired+=1
|
|
else: failed+=1
|
|
timers[index] += item.get_fire_interval()
|
|
return {"duration":seconds,"energy_start":start,"energy_end":player.energy,"shots":fired,"failed_shots":failed}
|
|
|
|
func movement_simulation() -> Dictionary:
|
|
player.velocity = Vector2.ZERO
|
|
for frame in 1200:
|
|
preload("res://scripts/game/flight_movement.gd").apply(player,1.0/60.0,0,true,false,0,true,false,player.max_speed*2)
|
|
var peak: float = player.velocity.length()
|
|
var seconds := 0.0
|
|
while player.velocity.length()>1 and seconds<120:
|
|
preload("res://scripts/game/flight_movement.gd").apply(player,1.0/60.0,0,false,false,0,false,false,player.max_speed*2)
|
|
seconds+=1.0/60.0
|
|
return {"tab_speed_after_20s":peak,"coast_to_stop_seconds":seconds,"thrust_acceleration":player.thrust_acceleration,"space_drag":player.space_drag}
|
|
|
|
func shield_simulation() -> Dictionary:
|
|
player.shields = 0.0
|
|
var seconds := 0.0
|
|
while player.shields<player.max_shields and seconds<300:
|
|
player.shields = minf(player.max_shields,player.shields+player.shield_regeneration/60.0)
|
|
seconds+=1.0/60.0
|
|
return {"empty_to_full_seconds":seconds,"regen_works_during_combat":true,"leech_on_1000_actual_damage":1000*player.shield_on_damage_percent/100.0}
|