mirror of
https://github.com/goonstation/goonstation-2020.git
synced 2026-07-14 08:32:20 +01:00
546 lines
15 KiB
Plaintext
546 lines
15 KiB
Plaintext
// flockdrone stuff, ask cirr or do a search for "flockdrone"
|
|
|
|
/////////////////////////////
|
|
// FLOCK DATUM
|
|
/////////////////////////////
|
|
// used to manage and share information between members of a flock/nest
|
|
/var/list/flocks = list()
|
|
/datum/flock
|
|
var/name
|
|
var/list/all_owned_tiles = list()
|
|
var/list/busy_tiles = list()
|
|
var/list/priority_tiles = list()
|
|
var/list/traces = list()
|
|
var/list/units = list()
|
|
var/list/enemies = list()
|
|
var/list/annotation_viewers = list()
|
|
var/list/annotations = list() // key is atom ref, value is image
|
|
var/mob/living/intangible/flock/flockmind/flockmind
|
|
var/snoop_clarity = 40 // how easily we can see silicon messages, how easily silicons can see this flock's messages
|
|
var/chui/window/flockpanel/panel
|
|
|
|
/datum/flock/New()
|
|
src.name = "[pick(consonants_lower)][pick(vowels_lower)].[pick(consonants_lower)][pick(vowels_lower)]"
|
|
flocks[src.name] = src
|
|
processing_items |= src
|
|
panel = new(src)
|
|
|
|
/datum/flock/proc/describe_state()
|
|
var/list/state = list()
|
|
state["update"] = "flock"
|
|
|
|
// DESCRIBE TRACES
|
|
var/list/tracelist = list()
|
|
for(var/mob/living/intangible/flock/trace/T in src.traces)
|
|
tracelist[++tracelist.len] = T.describe_state()
|
|
state["partitions"] = tracelist
|
|
|
|
// DESCRIBE DRONES
|
|
var/list/dronelist = list()
|
|
for(var/mob/living/critter/flock/drone/F in src.units)
|
|
dronelist[++dronelist.len] = F.describe_state()
|
|
state["drones"] = dronelist
|
|
|
|
// DESCRIBE ENEMIES
|
|
var/list/enemylist = list()
|
|
for(var/name in src.enemies)
|
|
var/list/enemy_stats = src.enemies[name]
|
|
var/mob/living/M = enemy_stats["mob"]
|
|
if(istype(M)) // fix runtime: Cannot read null.name
|
|
var/list/enemy = list()
|
|
enemy["name"] = M.name
|
|
enemy["area"] = enemy_stats["last_seen"]
|
|
enemy["ref"] = "\ref[M]"
|
|
enemylist[++enemylist.len] = enemy
|
|
else
|
|
// enemy no longer exists, let's do something about that
|
|
src.enemies -= name
|
|
state["enemies"] = enemylist
|
|
|
|
// DESCRIBE VITALS (do this last so we can report list lengths)
|
|
var/list/vitals = list()
|
|
vitals["name"] = src.name
|
|
vitals["drones"] = dronelist.len
|
|
vitals["partitions"] = tracelist.len
|
|
state["vitals"] = vitals
|
|
|
|
return state
|
|
|
|
/datum/flock/disposing()
|
|
flocks[src.name] = null
|
|
processing_items -= src
|
|
..()
|
|
|
|
/datum/flock/proc/total_health_percentage()
|
|
var/hp = 0
|
|
var/max_hp = 0
|
|
for(var/mob/living/critter/flock/F in src.units)
|
|
F.count_healths()
|
|
hp += F.health
|
|
max_hp += F.max_health
|
|
if(max_hp != 0)
|
|
return hp/max_hp
|
|
else
|
|
return 0
|
|
|
|
/datum/flock/proc/total_resources()
|
|
var/res = 0
|
|
for(var/mob/living/critter/flock/F in src.units)
|
|
res += F.resources
|
|
return res
|
|
|
|
/datum/flock/proc/registerFlockmind(var/mob/living/intangible/flock/flockmind/F)
|
|
if(!F)
|
|
return
|
|
src.flockmind = F
|
|
|
|
/datum/flock/proc/addTrace(var/mob/living/intangible/flock/trace/T)
|
|
if(!T)
|
|
return
|
|
src.traces |= T
|
|
|
|
// update the flock control panel
|
|
var/list/update = T.describe_state()
|
|
update["update"] = "add"
|
|
update["key"] = "partitions"
|
|
// ref is already provided
|
|
panel.PushUpdate(update)
|
|
|
|
/datum/flock/proc/removeTrace(var/mob/living/intangible/flock/trace/T)
|
|
if(!T)
|
|
return
|
|
src.traces -= T
|
|
|
|
// update the flock control panel
|
|
var/list/update = list()
|
|
update["update"] = "remove"
|
|
update["key"] = "partitions"
|
|
update["ref"] = "\ref[T]"
|
|
panel.PushUpdate(update)
|
|
|
|
// ANNOTATIONS
|
|
|
|
// currently both flockmind and player units get the same annotations: what tiles are marked for conversion, and who is shitlisted
|
|
/datum/flock/proc/showAnnotations(var/mob/M)
|
|
if(!M)
|
|
return
|
|
src.annotation_viewers |= M
|
|
var/client/C = M.client
|
|
if(C)
|
|
var/image/I
|
|
for(var/atom/key in src.annotations)
|
|
I = src.annotations[key]
|
|
if(istype(I, /image))
|
|
C.images |= I
|
|
|
|
/datum/flock/proc/hideAnnotations(var/mob/M)
|
|
if(!M)
|
|
return
|
|
src.annotation_viewers -= M
|
|
var/client/C = M.client
|
|
if(C)
|
|
var/image/I
|
|
for(var/atom/key in src.annotations)
|
|
I = src.annotations[key]
|
|
C.images -= I
|
|
|
|
// if anyone thinks they can optimise this PLEASE DO OH GOD - cirr, 2017
|
|
/datum/flock/proc/updateAnnotations()
|
|
var/image/I
|
|
var/list/valid_keys = list()
|
|
var/list/images_to_add = list()
|
|
// highlight priority tiles
|
|
for(var/turf/T in src.priority_tiles)
|
|
if(!(T in src.annotations))
|
|
// create a new image
|
|
I = image('icons/misc/featherzone.dmi', T, "frontier")
|
|
I.blend_mode = BLEND_ADD
|
|
I.alpha = 180
|
|
I.plane = PLANE_SELFILLUM + 1.1
|
|
// add to subscribers for annotations
|
|
images_to_add |= I
|
|
src.annotations[T] = I
|
|
// add key to list
|
|
valid_keys |= T
|
|
// highlight reserved tiles
|
|
for(var/name in src.busy_tiles)
|
|
var/turf/T = src.busy_tiles[name]
|
|
if(isturf(T) && !(T in src.annotations))
|
|
// create a new image
|
|
I = image('icons/misc/featherzone.dmi', T, "frontier")
|
|
I.blend_mode = BLEND_ADD
|
|
I.alpha = 80
|
|
I.plane = PLANE_SELFILLUM + 1.1
|
|
// add to subscribers for annotations
|
|
images_to_add |= I
|
|
src.annotations[T] = I
|
|
// add key to list
|
|
valid_keys |= T
|
|
// highlight enemies
|
|
for(var/name in src.enemies)
|
|
var/mob/B = src.enemies[name]["mob"]
|
|
if(!(B in src.annotations))
|
|
// create a new image
|
|
I = image('icons/misc/featherzone.dmi', B, "hazard")
|
|
I.blend_mode = BLEND_ADD
|
|
I.pixel_y = 16
|
|
I.plane = PLANE_SELFILLUM + 1.1
|
|
I.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
|
|
// add to subscribers for annotations
|
|
images_to_add |= I
|
|
src.annotations[B] = I
|
|
// add key to list
|
|
valid_keys |= B
|
|
var/list/to_remove = list()
|
|
for(var/atom/key in src.annotations)
|
|
if(!(key in valid_keys))
|
|
to_remove |= key
|
|
// add images
|
|
for(I in images_to_add)
|
|
for(var/mob/M in src.annotation_viewers)
|
|
var/client/C = M.client
|
|
if(C)
|
|
C.images += I
|
|
// purge images & annotation entries
|
|
for(var/atom/key in to_remove)
|
|
I = src.annotations[key]
|
|
src.annotations -= key
|
|
for(var/mob/M in src.annotation_viewers)
|
|
var/client/C = M.client
|
|
if(C)
|
|
C.images -= I
|
|
|
|
// UNITS
|
|
|
|
/datum/flock/proc/registerUnit(var/atom/movable/D)
|
|
if(isflock(D))
|
|
src.units |= D
|
|
if(src.panel && istype(D, /mob/living/critter/flock/drone))
|
|
var/mob/living/critter/flock/drone/drone = D
|
|
|
|
// update the flock control panel
|
|
var/list/update = drone.describe_state()
|
|
update["update"] = "add"
|
|
update["key"] = "drones"
|
|
// ref is already provided
|
|
panel.PushUpdate(update)
|
|
|
|
if(istype(D, /obj/flock_structure/egg))
|
|
src.units |= D
|
|
|
|
/datum/flock/proc/removeDrone(var/atom/movable/D)
|
|
if(isflock(D))
|
|
src.units -= D
|
|
|
|
// update the flock control panel
|
|
var/list/update = list()
|
|
update["update"] = "remove"
|
|
update["key"] = "drones"
|
|
update["ref"] = "\ref[D]"
|
|
panel.PushUpdate(update)
|
|
|
|
if(D:real_name && busy_tiles[D:real_name])
|
|
src.busy_tiles[D:real_name] = null
|
|
if(istype(D, /obj/flock_structure/egg))
|
|
src.units -= D
|
|
|
|
/datum/flock/proc/getComplexDroneCount()
|
|
var/count = 0
|
|
for(var/mob/living/critter/flock/drone/D in src.units)
|
|
count++
|
|
return count
|
|
|
|
// ENEMIES
|
|
|
|
/datum/flock/proc/updateEnemy(var/mob/living/M)
|
|
if(!M)
|
|
return
|
|
var/enemy_name = lowertext(M.name)
|
|
var/list/enemy_deets
|
|
if(!(enemy_name in src.enemies))
|
|
// add new
|
|
var/area/enemy_area = get_area(M)
|
|
enemy_deets = list()
|
|
enemy_deets["mob"] = M
|
|
enemy_deets["last_seen"] = enemy_area
|
|
src.enemies[enemy_name] = enemy_deets
|
|
|
|
// update the flock control panel
|
|
var/list/update = list()
|
|
update["update"] = "add"
|
|
update["key"] = "enemies"
|
|
update["ref"] = "\ref[M]"
|
|
update["name"] = M.name
|
|
update["area"] = enemy_area
|
|
panel.PushUpdate(update)
|
|
else
|
|
enemy_deets = src.enemies[enemy_name]
|
|
enemy_deets["last_seen"] = get_area(M)
|
|
// update annotations indicating enemies for flockmind and co
|
|
src.updateAnnotations()
|
|
|
|
/datum/flock/proc/removeEnemy(var/mob/living/M)
|
|
// call off all drones attacking this guy
|
|
for(var/name in src.enemies)
|
|
var/list/enemy_stats = src.enemies[name]
|
|
if(enemy_stats["mob"] == M)
|
|
src.enemies -= name
|
|
// update the flock control panel
|
|
var/list/update = list()
|
|
update["update"] = "remove"
|
|
update["key"] = "enemies"
|
|
update["ref"] = "\ref[M]"
|
|
panel.PushUpdate(update)
|
|
src.updateAnnotations()
|
|
|
|
/datum/flock/proc/isEnemy(var/mob/living/M)
|
|
var/enemy_name = lowertext(M.name)
|
|
return (enemy_name in src.enemies)
|
|
|
|
// DEATH
|
|
|
|
/datum/flock/proc/perish()
|
|
//cleanup as necessary
|
|
if(src.flockmind)
|
|
hideAnnotations(src.flockmind)
|
|
for(var/mob/M in src.units)
|
|
hideAnnotations(M)
|
|
all_owned_tiles = null
|
|
busy_tiles = null
|
|
priority_tiles = null
|
|
units = null
|
|
enemies = null
|
|
annotations = null
|
|
flockmind = null
|
|
qdel(src)
|
|
|
|
// TURFS
|
|
|
|
/datum/flock/proc/reserveTurf(var/turf/simulated/T, var/name)
|
|
if(T in all_owned_tiles)
|
|
return
|
|
src.busy_tiles[name] = T
|
|
src.updateAnnotations()
|
|
|
|
/datum/flock/proc/unreserveTurf(var/name)
|
|
src.busy_tiles -= name
|
|
src.updateAnnotations()
|
|
|
|
/datum/flock/proc/claimTurf(var/turf/simulated/T)
|
|
src.all_owned_tiles |= T
|
|
src.priority_tiles -= T // we have it now, it's no longer priority
|
|
src.updateAnnotations()
|
|
|
|
/datum/flock/proc/isTurfFree(var/turf/simulated/T, var/queryName) // provide the drone's name here: if they own the turf it's free _to them_
|
|
for(var/name in src.busy_tiles)
|
|
if(name == queryName)
|
|
continue
|
|
if(src.busy_tiles[name] == T)
|
|
return 0
|
|
return 1
|
|
|
|
/datum/flock/proc/togglePriorityTurf(var/turf/simulated/T)
|
|
if(!T)
|
|
return 1 // error!!
|
|
if(T in priority_tiles)
|
|
priority_tiles -= T
|
|
else
|
|
priority_tiles |= T
|
|
src.updateAnnotations()
|
|
|
|
// get closest unclaimed tile to requester
|
|
/datum/flock/proc/getPriorityTurfs(var/mob/living/critter/flock/drone/requester)
|
|
if(!requester)
|
|
return
|
|
if(src.busy_tiles[requester.name])
|
|
return src.busy_tiles[requester.name] // work on your claimed tile first you JERK
|
|
if(priority_tiles && priority_tiles.len)
|
|
var/list/available_tiles = priority_tiles
|
|
for(var/owner in src.busy_tiles)
|
|
available_tiles -= src.busy_tiles[owner]
|
|
return available_tiles
|
|
|
|
// PROCESS
|
|
|
|
/datum/flock/proc/process()
|
|
var/list/floors_no_longer_existing = list()
|
|
// check all active floors
|
|
for(var/turf/simulated/floor/feather/T in src.all_owned_tiles)
|
|
if(!T || T.loc == null || T.broken)
|
|
// tile got killed, remove it
|
|
floors_no_longer_existing |= T
|
|
continue
|
|
// check adjacent tiles to see if we've been surrounded and can start generating, or if we're no longer surrounded and can't generate
|
|
// var/validNeighbors = 0
|
|
// var/list/neighbors = getNeighbors(T, cardinal)
|
|
// for(var/turf/simulated/floor/feather/F in neighbors)
|
|
// validNeighbors += 1
|
|
// if(validNeighbors < 4 && T.generating)
|
|
// T.off()
|
|
// else if(validNeighbors >= 4 && !T.generating)
|
|
// T.on()
|
|
|
|
if(floors_no_longer_existing.len > 0)
|
|
src.all_owned_tiles -= floors_no_longer_existing
|
|
|
|
/datum/flock/proc/convert_turf(var/turf/T, var/converterName)
|
|
src.unreserveTurf(converterName)
|
|
src.claimTurf(flock_convert_turf(T))
|
|
playsound(T, "sound/items/Deconstruct.ogg", 70, 1)
|
|
|
|
////////////////////
|
|
// GLOBAL PROCS!!
|
|
////////////////////
|
|
|
|
// made into a global proc so a reagent can use it
|
|
// simple enough: if object path matches key, replace with instance of value
|
|
// if value is null, just delete object
|
|
/var/list/flock_conversion_paths = list(
|
|
/obj/grille = /obj/grille/flock,
|
|
/obj/window = /obj/window/feather,
|
|
/obj/machinery/door/airlock = /obj/machinery/door/feather,
|
|
/obj/machinery/door = null,
|
|
/obj/stool = /obj/stool/chair/comfy/flock,
|
|
/obj/table = /obj/table/flock/auto,
|
|
/obj/lattice = /obj/lattice/flock,
|
|
/obj/machinery/light = /obj/machinery/light/flock,
|
|
/obj/storage/closet = /obj/storage/closet/flock,
|
|
/obj/storage/secure/closet = /obj/storage/closet/flock
|
|
)
|
|
/proc/flock_convert_turf(var/turf/T)
|
|
if(!T)
|
|
return
|
|
|
|
// take light values to copy over
|
|
var/RL_LumR = T.RL_LumR
|
|
var/RL_LumG = T.RL_LumG
|
|
var/RL_LumB = T.RL_LumB
|
|
var/RL_AddLumR = T.RL_AddLumR
|
|
var/RL_AddLumG = T.RL_AddLumG
|
|
var/RL_AddLumB = T.RL_AddLumB
|
|
|
|
for(var/obj/O in T)
|
|
if(istype(O, /obj/machinery/door/feather))
|
|
// repair door
|
|
var/obj/machinery/door/feather/door = O
|
|
door.heal_damage()
|
|
animate_flock_convert_complete(O)
|
|
for(var/keyPath in flock_conversion_paths)
|
|
var/obj/replacementPath = flock_conversion_paths[keyPath]
|
|
if(istype(O, keyPath))
|
|
if(isnull(replacementPath))
|
|
qdel(O)
|
|
else
|
|
var/dir = O.dir
|
|
var/obj/converted = new replacementPath(T)
|
|
// if the object is a closet, it might not have spawned its contents yet
|
|
// so force it to do that first
|
|
if(istype(O, /obj/storage))
|
|
var/obj/storage/S = O
|
|
if(!isnull(S.spawn_contents))
|
|
S.make_my_stuff()
|
|
// if the object has contents, move them over!!
|
|
for (var/obj/OO in O)
|
|
OO.set_loc(converted)
|
|
for (var/mob/M in O)
|
|
M.set_loc(converted)
|
|
qdel(O)
|
|
converted.dir = dir
|
|
animate_flock_convert_complete(converted)
|
|
|
|
// if floor, turn to floor, if wall, turn to wall
|
|
if(istype(T, /turf/simulated/floor))
|
|
if(istype(T, /turf/simulated/floor/feather))
|
|
// fix instead of replace
|
|
var/turf/simulated/floor/feather/TF = T
|
|
TF.repair()
|
|
animate_flock_convert_complete(T)
|
|
else
|
|
T.ReplaceWith("/turf/simulated/floor/feather", 0)
|
|
animate_flock_convert_complete(T)
|
|
if(istype(T, /turf/simulated/wall))
|
|
T.ReplaceWith("/turf/simulated/wall/auto/feather", 0)
|
|
animate_flock_convert_complete(T)
|
|
|
|
|
|
if(istype(T, /turf/space))
|
|
// if we have a fibrenet, make it a floor
|
|
var/obj/lattice/flock/FL = locate(/obj/lattice/flock) in T
|
|
if(istype(FL))
|
|
qdel(FL)
|
|
T.ReplaceWith("/turf/simulated/floor/feather", 0)
|
|
animate_flock_convert_complete(T)
|
|
// if we have no fibrenet, make one
|
|
else
|
|
FL = new(T)
|
|
animate_flock_convert_complete(FL)
|
|
else // don't do this stuff if the turf is space, it fucks it up more
|
|
T.RL_Cleanup()
|
|
T.RL_UpdateLight()
|
|
T.RL_LumR = RL_LumR
|
|
T.RL_LumG = RL_LumG
|
|
T.RL_LumB = RL_LumB
|
|
T.RL_AddLumR = RL_AddLumR
|
|
T.RL_AddLumG = RL_AddLumG
|
|
T.RL_AddLumB = RL_AddLumB
|
|
T.RL_UpdateLight()
|
|
|
|
return T
|
|
|
|
/proc/mass_flock_convert_turf(var/turf/T)
|
|
// a terrible idea
|
|
if(!T)
|
|
T = get_turf(usr)
|
|
if(!T)
|
|
return // not sure if this can happen, so it will
|
|
|
|
flock_spiral_conversion(T)
|
|
|
|
/proc/radial_flock_conversion(var/atom/movable/source, var/max_radius=20)
|
|
if(!source) return
|
|
var/turf/T = get_turf(source)
|
|
var/radius = 1
|
|
while(radius <= max_radius)
|
|
var/list/turfs = circular_range(T, radius)
|
|
LAGCHECK(LAG_LOW)
|
|
for(var/turf/tile in turfs)
|
|
if(istype(tile, /turf/simulated) && !isfeathertile(tile))
|
|
flock_convert_turf(tile)
|
|
sleep(0.5)
|
|
LAGCHECK(LAG_LOW)
|
|
radius++
|
|
sleep(radius * 10)
|
|
if(isnull(source))
|
|
return // our source is gone, stop the process
|
|
|
|
|
|
/proc/flock_spiral_conversion(var/turf/T)
|
|
if(!T) return
|
|
// spiral algorithm adapted from https://stackoverflow.com/questions/398299/looping-in-a-spiral
|
|
var/ox = T.x
|
|
var/oy = T.y
|
|
var/x = 0
|
|
var/y = 0
|
|
var/z = T.z
|
|
var/dx = 0
|
|
var/dy = -1
|
|
var/temp = 0
|
|
|
|
while(isturf(T))
|
|
if(istype(T, /turf/simulated) && !isfeathertile(T))
|
|
// do stuff to turf
|
|
flock_convert_turf(T)
|
|
sleep(2)
|
|
LAGCHECK(LAG_LOW)
|
|
// figure out where next turf is
|
|
if (x == y || (x < 0 && x == -y) || (x > 0 && x == 1-y))
|
|
temp = dx
|
|
dx = -dy
|
|
dy = temp
|
|
x += dx
|
|
y += dy
|
|
// get next turf
|
|
T = locate(ox + x, oy + y, z)
|
|
|
|
|