Files
CHOMPStation2/code/modules/rogueminer_vr/zonemaster.dm
Leshana c837078105 Replaced "area" shuttles with "landmark" shuttles.
Largely ported from the work done at Baystation in https://github.com/Baystation12/Baystation12/pull/17460 and later commits.

 - Shuttles no longer require a separate area for each location they jump to.
   Instead destinations are indicated by landmark objects, which are not necessarily exclusive to that shuttle.
   This means that more than one shuttle could use the same docking port (not at the same time of course).
 - Enhanced shuttle control computers to use nanoui if they didn't.
 - Organizes shuttle datum code a bit better so there is less re-inventing the wheel in subtypes.
 - Allows the possibility of shuttles (or destinations) that start on late-loaded maps.
 - Deprecate the "extra" shuttle areas that are no longer needed and update shuttle areas in unit tests

This all required a bit of infrastructure improvements.

 - ChangeArea proc, for changing the area of a turf.
 - Fixed lighting overlays actually being able to be destroyed.
 - Added a few utility macros and procs.
 - Added "turf translation" procs which are like move_contents_to but more flexible.
2020-03-05 10:29:08 -05:00

431 lines
14 KiB
Plaintext

//////////////////////////////
// The zonemaster object, spawned to track the zone and
// clean/populate the zone with asteroids and loot
//////////////////////////////
/datum/rogue/zonemaster
//our area
var/area/asteroid/rogue/myarea
// var/area/shuttle/belter/myshuttle
var/obj/effect/shuttle_landmark/myshuttle_landmark
//world.time
var/prepared_at = 0
//accepting shuttles
var/ready = 0
//completely empty
var/clean = 1
//scored or not
var/scored = 0
//for scoring
var/list/mineral_rocks = list()
var/list/spawned_mobs = list()
var/original_mobs = 0
//in-use spawns from the area
var/obj/asteroid_spawner/list/rockspawns = list()
var/obj/rogue_mobspawner/list/mobspawns = list()
/datum/rogue/zonemaster/New(var/area/A)
ASSERT(A)
myarea = A
myshuttle_landmark = locate(/obj/effect/shuttle_landmark) in myarea
if(!istype(myshuttle_landmark))
warning("Zonemaster cannot find a shuttle landmark in its area '[A]'")
spawn(10) //This is called from controller New() and freaks out if this calls back too fast.
rm_controller.mark_clean(src)
///////////////////////////////
///// Utility Procs ///////////
///////////////////////////////
/datum/rogue/zonemaster/proc/is_occupied()
var/humans = 0
for(var/mob/living/carbon/human/H in human_mob_list)
if(H.stat >= DEAD) //Conditions for exclusion here, like if disconnected people start blocking it.
continue
var/area/A = get_area(H)
if(A == myarea) //The loc of a turf is the area it is in.
humans++
return humans
///////////////////////////////
///// Asteroid Generation /////
///////////////////////////////
/datum/rogue/zonemaster/proc/generate_asteroid(var/core_min = 2, var/core_max = 5)
//Chance for a predefined structure instead, more common later
if(prob(rm_controller.diffstep*4))
rm_controller.dbg("ZM(ga): Fell into prefab asteroid chance.")
var/prefab = pick(rm_controller.prefabs["tier[rm_controller.diffstep]"])
rm_controller.dbg("ZM(ga): Picked [prefab] as the prefab.")
var/prefabinst = new prefab(null)
return prefabinst
var/datum/rogue/asteroid/A = new(rand(core_min,core_max))
rm_controller.dbg("ZM(ga): New asteroid with C:[A.coresize], TW:[A.type_wall].")
//Add the core to the asteroid's map
rm_controller.dbg("ZM(ga): Starting core generation for [A.coresize] size core..")
for(var/x = 1; x <= A.coresize, x++)
for(var/y = 1; y <= A.coresize, y++)
rm_controller.dbg("ZM(ga): Doing core-relative [x],[y] at [A.coresize+x],[A.coresize+y], [A.type_wall].")
A.spot_add(A.coresize+x, A.coresize+y, A.type_wall)
var/max_armlen = A.coresize - 1 //Can tweak to change appearance.
//Add the arms to the asteroid's map
//Vertical arms
for(var/x = A.coresize+1, x <= A.coresize*2, x++) //Start at leftmost side of core, work towards higher X.
rm_controller.dbg("ZM(ga): Vert arms. My current column is x:[x].")
var/B_arm = rand(0,max_armlen)
var/T_arm = rand(0,max_armlen)
rm_controller.dbg("ZM(ga): B/T. Going to make B:[B_arm], T:[T_arm] for x:[x].")
//Bottom arm
for(var/y = A.coresize, y > A.coresize-B_arm, y--) //Start at bottom edge of the core, work towards lower Y.
A.spot_add(x,y,A.type_wall)
//Top arm
for(var/y = (A.coresize*2)+1, y < ((A.coresize*2)+1)+T_arm, y++) //Start at top edge of the core, work towards higher Y.
A.spot_add(x,y,A.type_wall)
//Horizontal arms
for(var/y = A.coresize+1, y <= A.coresize*2, y++) //Start at lower side of core, work towards higher Y.
rm_controller.dbg("ZM(ga): Horiz arms. My current row is y:[y].")
var/R_arm = rand(0,max_armlen)
var/L_arm = rand(0,max_armlen)
rm_controller.dbg("ZM(ga): R/L. Going to make R:[R_arm], L:[L_arm] for y:[y].")
//Right arm
for(var/x = (A.coresize*2)+1, x <= ((A.coresize*2)+1)+R_arm, x++) //Start at right edge of core, work towards higher X.
A.spot_add(x,y,A.type_wall)
//Left arm
for(var/x = A.coresize, x > A.coresize-L_arm, x--) //Start at left edge of core, work towards lower X.
A.spot_add(x,y,A.type_wall)
//Diagonals
// hao do
rm_controller.dbg("ZM(ga): Asteroid generation done.")
return A
/datum/rogue/zonemaster/proc/place_asteroid(var/datum/rogue/asteroid/A,var/obj/asteroid_spawner/SP)
ASSERT(SP && A)
rm_controller.dbg("ZM(pa): Placing at point [SP.x],[SP.y],[SP.z].")
SP.myasteroid = A
//Bottom-left corner of our bounding box
var/BLx = SP.x - (A.width/2)
var/BLy = SP.y - (A.width/2)
rm_controller.dbg("ZM(pa): BLx is [BLx], BLy is [BLy].")
rm_controller.dbg("ZM(pa): The asteroid has [A.map.len] X-lists.")
for(var/Ix=1, Ix <= A.map.len, Ix++)
var/list/curr_x = A.map[Ix]
rm_controller.dbg("ZM(pa): Now doing X:[Ix] which has [curr_x.len] Y-lists.")
for(var/Iy=1, Iy <= curr_x.len, Iy++)
var/list/curr_y = curr_x[Iy]
rm_controller.dbg("ZM(pa): Now doing Y:[Iy] which has [curr_y.len] items.")
var/world_x = BLx+Ix
var/world_y = BLy+Iy
var/world_z = SP.z
var/spot = locate(world_x,world_y,world_z)
for(var/T in curr_y)
rm_controller.dbg("ZM(pa): Doing entry [T] in Y-list [Iy].")
if(ispath(T,/turf)) //We're spawning a turf
rm_controller.dbg("ZM(pa): Turf-generate mode.")
//Make sure we locate()'d a turf and not something else
if(!isturf(spot))
spot = get_turf(spot)
var/turf/P = spot
rm_controller.dbg("ZM(pa): Replacing [P.type] with [T].")
var/turf/newturf = P.ChangeTurf(T)
switch(newturf.type)
if(/turf/simulated/mineral/vacuum)
place_resources(newturf)
newturf.update_icon(1)
else //Anything not a turf
rm_controller.dbg("ZM(pa): Creating [T].")
new T(spot)
/datum/rogue/zonemaster/proc/place_resources(var/turf/simulated/mineral/M)
#define XENOARCH_SPAWN_CHANCE 0.3
#define DIGSITESIZE_LOWER 4
#define DIGSITESIZE_UPPER 12
#define ARTIFACTSPAWNNUM_LOWER 6
#define ARTIFACTSPAWNNUM_UPPER 12 //Replace with difficulty-based ones.
if(!M.mineral && prob(rm_controller.diffstep_chances[rm_controller.diffstep])) //Difficulty translates directly into ore chance
rm_controller.dbg("ZM(par): Adding mineral to [M.x],[M.y].")
M.make_ore(rm_controller.diffstep >= 3 ? 1 : 0)
mineral_rocks += M
//If above difficulty threshold make rare ore instead (M.make_ore(1))
//Increase with difficulty etc
if(!M.density)
return
if(isnull(M.geologic_data))
M.geologic_data = new /datum/geosample(M)
if(!prob(XENOARCH_SPAWN_CHANCE))
return
var/farEnough = 1
for(var/A in SSxenoarch.digsite_spawning_turfs)
var/turf/T = A
if(T in range(5, M))
farEnough = 0
break
if(!farEnough)
return
SSxenoarch.digsite_spawning_turfs.Add(M)
var/digsite = get_random_digsite_type()
var/target_digsite_size = rand(DIGSITESIZE_LOWER, DIGSITESIZE_UPPER)
var/list/processed_turfs = list()
var/list/turfs_to_process = list(M)
var/list/viable_adjacent_turfs = list()
if(target_digsite_size > 1)
for(var/turf/simulated/mineral/T in orange(2, M))
if(!T.density)
continue
if(T.finds)
continue
if(T in processed_turfs)
continue
viable_adjacent_turfs.Add(T)
target_digsite_size = min(target_digsite_size, viable_adjacent_turfs.len)
for(var/i = 1 to target_digsite_size)
turfs_to_process += pick_n_take(viable_adjacent_turfs)
while(turfs_to_process.len)
var/turf/simulated/mineral/archeo_turf = pop(turfs_to_process)
rm_controller.dbg("ZM(par): Adding archeo find to [M.x],[M.y].")
processed_turfs.Add(archeo_turf)
if(isnull(archeo_turf.finds))
archeo_turf.finds = list()
if(prob(50))
archeo_turf.finds.Add(new /datum/find(digsite, rand(10, 190)))
else if(prob(75))
archeo_turf.finds.Add(new /datum/find(digsite, rand(10, 90)))
archeo_turf.finds.Add(new /datum/find(digsite, rand(110, 190)))
else
archeo_turf.finds.Add(new /datum/find(digsite, rand(10, 50)))
archeo_turf.finds.Add(new /datum/find(digsite, rand(60, 140)))
archeo_turf.finds.Add(new /datum/find(digsite, rand(150, 190)))
//sometimes a find will be close enough to the surface to show
var/datum/find/F = archeo_turf.finds[1]
if(F.excavation_required <= F.view_range)
archeo_turf.archaeo_overlay = "overlay_archaeo[rand(1,3)]"
archeo_turf.update_icon()
//have a chance for an artifact to spawn here, but not in animal or plant digsites
if(isnull(M.artifact_find) && digsite != DIGSITE_GARDEN && digsite != DIGSITE_ANIMAL)
SSxenoarch.artifact_spawning_turfs.Add(archeo_turf)
//create artifact machinery
var/num_artifacts_spawn = rand(ARTIFACTSPAWNNUM_LOWER, ARTIFACTSPAWNNUM_UPPER)
while(SSxenoarch.artifact_spawning_turfs.len > num_artifacts_spawn)
pick_n_take(SSxenoarch.artifact_spawning_turfs)
var/list/artifacts_spawnturf_temp = SSxenoarch.artifact_spawning_turfs.Copy()
while(artifacts_spawnturf_temp.len > 0)
var/turf/simulated/mineral/artifact_turf = pop(artifacts_spawnturf_temp)
artifact_turf.artifact_find = new()
#undef XENOARCH_SPAWN_CHANCE
#undef DIGSITESIZE_LOWER
#undef DIGSITESIZE_UPPER
#undef ARTIFACTSPAWNNUM_LOWER
#undef ARTIFACTSPAWNNUM_UPPER //Replace with difficulty-based ones.
///////////////////////////////
///// Zone Population /////////
///////////////////////////////
//Overall 'prepare' proc (marks as ready)
/datum/rogue/zonemaster/proc/prepare_zone(var/delay = 0)
rm_controller.unmark_clean(src)
rm_controller.dbg("ZM(p): Preparing zone with difficulty level [rm_controller.diffstep].")
rm_controller.dbg("ZM(p): Randomizing spawns.")
randomize_spawns()
rm_controller.dbg("ZM(p): [rockspawns.len] picked.")
for(var/obj/asteroid_spawner/SP in rockspawns)
rm_controller.dbg("ZM(p): Creating asteroid for [SP.x],[SP.y],[SP.z].")
var/datum/rogue/asteroid/A = generate_asteroid()
rm_controller.dbg("ZM(p): Placing asteroid.")
place_asteroid(A,SP)
if(delay)
sleep(delay)
for(var/obj/rogue_mobspawner/SP in mobspawns)
rm_controller.dbg("ZM(p): Spawning mob at [SP.x],[SP.y],[SP.z].")
//Make sure we can spawn a spacemob here
if(!istype(get_turf(SP),/turf/space))
rm_controller.dbg("ZM(p): Turf blocking mob spawn at [SP.x],[SP.y],[SP.z].")
mobspawns -= SP
for(var/obj/rogue_mobspawner/NS in myarea.mob_spawns)
if(NS in mobspawns)
continue
if(istype(get_turf(NS),/turf/space))
SP = NS
break
if(SP)
rm_controller.dbg("ZM(p): Got a mob spawnpoint, so picking a type.")
var/mobchoice = pickweight(rm_controller.mobs["tier[rm_controller.diffstep]"])
rm_controller.dbg("ZM(p): Picked [mobchoice] to spawn.")
var/mob/living/newmob = new mobchoice(get_turf(SP))
newmob.faction = "asteroid_belt"
spawned_mobs += newmob
if(delay)
sleep(delay)
rm_controller.dbg("ZM(p): Zone generation done.")
to_world_log("RM(stats): PREP [myarea] at [world.time] with [spawned_mobs.len] mobs, [mineral_rocks.len] minrocks, total of [rockspawns.len] rockspawns, [mobspawns.len] mobspawns.") //DEBUG code for playtest stats gathering.
prepared_at = world.time
rm_controller.mark_ready(src)
return myarea
//Randomize the landmarks that are enabled
/datum/rogue/zonemaster/proc/randomize_spawns(var/chance = 50)
rm_controller.dbg("ZM(rs): Previously [rockspawns.len] rockspawns.")
rockspawns.Cut()
rm_controller.dbg("ZM(rs): Now [rockspawns.len] rockspawns.")
for(var/obj/asteroid_spawner/SP in myarea.asteroid_spawns)
if(prob(chance))
rockspawns += SP
rm_controller.dbg("ZM(rs): Picked [rockspawns.len] new rockspawns with [chance]% chance.")
rm_controller.dbg("ZM(rs): Previously [mobspawns.len] mobspawns.")
mobspawns.Cut()
rm_controller.dbg("ZM(rs): Now [mobspawns.len] mobspawns.")
for(var/obj/rogue_mobspawner/SP in myarea.mob_spawns)
if(prob(rm_controller.diffstep_chances[rm_controller.diffstep]))
mobspawns += SP
original_mobs++
rm_controller.dbg("ZM(rs): Picked [mobspawns.len] new mobspawns with [chance]% chance.")
return myarea
///////////////////////////////
///// Zone Cleaning ///////////
///////////////////////////////
/datum/rogue/zonemaster/proc/score_zone(var/bonus = 10)
rm_controller.dbg("ZM(sz): Scoring zone with area [myarea].")
scored = 1
var/tally = bonus
//Ore-bearing rocks that were mined
for(var/turf/T in mineral_rocks)
var/has_minerals = 0
for(var/atom/I in T.contents)
if(istype(I,/obj/effect/mineral))
has_minerals++
break
if(has_minerals == 0)
tally += RM_DIFF_VALUE_ORE
mineral_rocks.Cut() //For good measure, to prevent rescoring.
for(var/I = 1, I <= spawned_mobs.len, I++)
if(isnull(spawned_mobs[I]))
tally += RM_DIFF_VALUE_MOB //Mobs so annihilated they were deleted
rm_controller.dbg("ZM(sz): Scoring one mob annihilated.")
if(istype(spawned_mobs[I],/mob))
var/mob/M = spawned_mobs[I]
if(M.stat > 0) //Knocked out or dead or anything other than normal
tally += RM_DIFF_VALUE_MOB
rm_controller.dbg("ZM(sz): Scoring one mob dead.")
spawned_mobs.Cut()
original_mobs = 0
rm_controller.adjust_difficulty(tally)
rm_controller.dbg("ZM(sz): Finished scoring and adjusted by [tally].")
to_world_log("RM(stats): SCORE [myarea] for [tally].") //DEBUG code for playtest stats gathering.
return tally
//Overall 'destroy' proc (marks as unready)
/datum/rogue/zonemaster/proc/clean_zone(var/delay = 1)
rm_controller.dbg("ZM(cz): Cleaning zone with area [myarea].")
to_world_log("RM(stats): CLEAN start [myarea] at [world.time] prepared at [prepared_at].") //DEBUG code for playtest stats gathering.
rm_controller.unmark_ready(src)
//Cut these lists so qdel can dereference the things properly
mineral_rocks.Cut()
spawned_mobs.Cut()
rockspawns.Cut()
mobspawns.Cut()
var/ignored = list(
/obj/asteroid_spawner,
/obj/rogue_mobspawner,
/obj/effect/shuttle_landmark,
/obj/effect/step_trigger/teleporter/roguemine_loop/north,
/obj/effect/step_trigger/teleporter/roguemine_loop/south,
/obj/effect/step_trigger/teleporter/roguemine_loop/east,
/obj/effect/step_trigger/teleporter/roguemine_loop/west)
for(var/atom/I in myarea.contents)
if(I.type == /turf/space)
I.overlays.Cut()
continue
else if(!I.simulated)
continue
else if(I.type in ignored)
continue
qdel(I)
sleep(delay)
//A deletion so nice that I give it twice
for(var/atom/I in myarea.contents)
if(I.type == /turf/space)
I.overlays.Cut()
continue
else if(!I.simulated)
continue
else if(I.type in ignored)
continue
qdel(I)
sleep(delay)
//Clean up vars
scored = 0
original_mobs = 0
prepared_at = 0
to_world_log("RM(stats): CLEAN done [myarea] at [world.time].") //DEBUG code for playtest stats gathering.
rm_controller.dbg("ZM(cz): Finished cleaning up zone area [myarea].")
rm_controller.mark_clean(src)
return myarea
///////////////////////////////
///// Mysterious Mystery //////
///////////////////////////////
//Throw a meteor at a player in the zone