Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions code/game/objects/effects/fire_blast.dm
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,7 @@
adjusted_fire_damage = fire_damage * 0.25

spawn()
if(spread && current_step >= spread_start && blast_age < 4)
var/turf/TS = get_turf(src)
for(var/turf/TU in range(1, TS))
if(TU != get_turf(src))
var/tilehasfire = 0
var/obstructed = 0
for(var/obj/effect/E in TU)
if(istype(E, /obj/effect/fire_blast))
tilehasfire = 1
for(var/obj/machinery/door/D in TU)
if(istype(D, /obj/machinery/door/airlock) || istype(D, /obj/machinery/door/mineral))
if(D.density)
obstructed = 1
if(prob(spread_chance) && TS.Adjacent(TU) && !TU.density && !tilehasfire && !obstructed)
new type(TU, fire_damage, current_step, blast_age+1, pressure, blast_temperature, duration)
sleep(1)
blast_spread(current_step, pressure, blast_temperature)

spawn()
while(world.time < began_life + duration)
Expand Down Expand Up @@ -115,6 +100,24 @@
else
L.adjustFireLoss(adjusted_fire_damage)

/obj/effect/fire_blast/proc/blast_spread(current_step, pressure, blast_temperature)
if(spread && current_step >= spread_start && blast_age < 4)
var/turf/TS = get_turf(src)
for(var/turf/TU in range(1, TS))
if(TU != get_turf(src))
var/tilehasfire = 0
var/obstructed = 0
for(var/obj/effect/E in TU)
if(istype(E, /obj/effect/fire_blast))
tilehasfire = 1
for(var/obj/machinery/door/D in TU)
if(istype(D, /obj/machinery/door/airlock) || istype(D, /obj/machinery/door/mineral))
if(D.density)
obstructed = 1
if(prob(spread_chance) && TS.Adjacent(TU) && !TU.density && !tilehasfire && !obstructed)
new type(TU, fire_damage, current_step, blast_age+1, pressure, blast_temperature, duration)
sleep(1)

/obj/effect/fire_blast/dragonbreath/burn_mob(mob/living/L, var/adjusted_fire_damage, var/origin)
if(L.mutations.Find(M_RESIST_HEAT)) //Heat resistance protects you from fear
return ..()
Expand Down
66 changes: 66 additions & 0 deletions code/modules/admin/admin.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1658,3 +1658,69 @@ var/alien_ship_location = 1 // 0 = base , 1 = mine
dat += "<br/>"

usr << browse(HTML_SKELETON(dat), "window=rodswindow;size=350x300")

/datum/admins/proc/beasts_panel()

var/dat = {"<html>
<head>
<title>Megabeast Panel</title>
<style>
table,h2 {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
}
td, th {
border: 1px solid #dddddd;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
</style>
</head>
<body>
<h2 style="text-align:center">Megabeast Panel</h2>
<table>
<tr>
<th style="width:1%">Mob</th>
<th style="width:1%">Name</th>
<th style="width:1%">Datum Info</th>
<th style="width:2%">Ability</th>
<th style="width:2%"><a href='?src=\ref[src];create_megabeast=1'>New</a></th><!-- Spawn a random FB -->
</tr>
"}

for(var/datum/procedural_mobspawn/ID in procgen_mob_datums)
var/abilityname = "None"
var/passivename = ""
if(ID.ranged)
if(ID.mybreath)
abilityname = "Breath: [ID.mybreath.name]"
else if(ID.projectiletype)
abilityname = "Projectile: [ID.projectiletype.name]"
if(ID.radioactive)
passivename += "Radiation Pulse"
if(ID.vapors)
passivename += "[ID.vapors.name] Smoke"

dat += {"<tr>
<td>[bicon(ID)]</td>
<td>[ID.name]</td>
<td><a href='?_src_=vars;Vars=\ref[ID]'>\[VV\]</a> <a href='?_src_=vars;mark_object=\ref[ID]'>\[mark datum\]</a></td>
<td>[abilityname]<br>[passivename]</br></td>
<td><a href='?src=\ref[src];create_megabeast=\ref[ID]'>Spawn</a></td><!-- Spawn this FB specifically.-->
</tr>
"}//<FONT SIZE=2><A href='?src=\ref[src];ac_censor_channel_author=\ref[src.admincaster_feed_channel]'>[(src.admincaster_feed_channel.author=="\[REDACTED\]") ? ("Undo Author censorship") : ("Censor channel Author")]</A></FONT><HR>

dat += {"</table>
</body>
</html>
"}

usr << browse(HTML_SKELETON(dat), "window=beastspanel;size=840x450")

/datum/admins/proc/create_megabeast(var/datum/procedural_mobspawn/add_template)
if(!add_template)
new /datum/procedural_mobspawn()
return
new /mob/living/simple_animal/hostile/forgotten_beast(get_turf(usr), add_template)
13 changes: 11 additions & 2 deletions code/modules/admin/admin_verbs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ var/list/admin_verbs_admin = list(
/client/proc/body_archive_panel,
/client/proc/climate_panel,
/datum/admins/proc/ashInvokedEmotions, /*Ashes all paper from the invoke emotion spell. An emergency purge.*/
/client/proc/toggle_admin_examine
)
/client/proc/toggle_admin_examine,
/client/proc/beasts_panel /* Lists all forgotten beasts generated, along with their characteristics */
)
var/list/admin_verbs_ban = list(
/client/proc/unban_panel,
/client/proc/jobbans,
Expand Down Expand Up @@ -1428,3 +1429,11 @@ fieldset {width:140px;}
to_chat(usr, "<span class='notice'>You toggle [holder.admin_examine ? "on" : "off"] admin examining.")
feedback_add_details("admin_verb","admin_examine")
return

/client/proc/beasts_panel()
set name = "Megabeast Panel"
set category = "Admin"
if(holder)
holder.beasts_panel()
log_admin("[key_name(usr)] checked the Megabeast Panel.")
feedback_add_details("admin_verb","BST")
6 changes: 6 additions & 0 deletions code/modules/admin/topic.dm
Original file line number Diff line number Diff line change
Expand Up @@ -3234,6 +3234,12 @@
return
return create_mob(usr)

else if(href_list["create_megabeast"])
if(!check_rights(0))
return
var/datum/D = locate(href_list["create_megabeast"])
return create_megabeast(D)

else if(href_list["object_list"]) //this is the laggiest thing ever
if(!check_rights(R_SPAWN))
return
Expand Down
156 changes: 156 additions & 0 deletions code/modules/mob/living/simple_animal/hostile/megabeast.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/obj/effect/landmark/procedural_mobspawn/forgottenbeast
name = "forgotten beast spawner"
desc = "You shouldn't be seeing this"
icon = 'icons/mob/screen1.dmi'
icon_state = "x2"
var/mob/living/simple_animal/hostile/mobtype

/obj/effect/landmark/procedural_mobspawn/forgottenbeast/New()
SpawnMob(mobtype)

/obj/effect/landmark/procedural_mobspawn/forgottenbeast/proc/SpawnMob()
new /mob/living/simple_animal/hostile/forgotten_beast(get_turf(src), new /datum/procedural_mobspawn(mobtype))
qdel(src)

/*
//Megabeast Template
//Basic beast template.
//Arguments: loc for spawn location
//(optional) add_template for a pre-chosen procgen datum to template off of. If none is provided, it will pick one at random from the existing list. If none exist, it will make one.
//
//refer to procedural_mobspawn for the datums
*/
/mob/living/simple_animal/hostile/forgotten_beast
name = "Forgotten Beast"
desc = "Some indescribable horror."
health = 1000
maxHealth = 1000
icon = 'icons/mob/animal.dmi'
icon_state = "otherthing"
icon_dead = "otherthing-dead"
attack_sound = 'sound/weapons/heavysmash.ogg'
faction = "megabeast"
min_oxy = 0
max_oxy = 0
min_tox = 0
max_tox = 0
min_co2 = 0
max_co2 = 0
min_n2 = 0
max_n2 = 0
environment_smash_flags = SMASH_LIGHT_STRUCTURES | SMASH_CONTAINERS | SMASH_WALLS
size = SIZE_HUGE
a_intent = I_HURT
var/picked
var/mob/living/simple_animal/hostile/mymob
var/list/mob_types
var/list/breath_types = list()
var/datum/reagent/vapors
var/radioactive
var/pulse_cooldown = 0
var/special_cooldown
var/breath_damage = 10
var/breath_damage_type = BRUTE
var/datum/custom_breath/mybreath
var/datum/procedural_mobspawn/template
var/obj/loot
var/loot_count

/mob/living/simple_animal/hostile/forgotten_beast/Life()
..()
if(radioactive)
if(world.time > pulse_cooldown +20 SECONDS)
rad_blast()
if(vapors)
if(world.time > pulse_cooldown +60 SECONDS)
GasAttack()

/mob/living/simple_animal/hostile/forgotten_beast/death(var/gibbed = FALSE)
if(!gibbed)
gib()
return
for(var/i = loot_count; i > 0)
new loot(get_turf(src))
--i
new /obj/effect/gibspawner/generic(src.loc)
qdel(src)
Comment on lines +75 to +76
Copy link
Collaborator

@jwhitak jwhitak Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the proper recursion, you should call this proc at the end. It'll handle the cleanup of things like list references (hard dels), possessed clients, etc. gib() will call its own gibs. If you want more, you can technically use the gibs(loc) proc (which is just the above spawner, but more future proofed in case we change gibs in the future)

Flow is beast dies -> death(0) called -> gib() called -> gib calls death(1) -> skips first if, spawns loot, with suggested change enters parent procs to handle weird situations -> finishes -> returns to gib proc -> gib removes the mobs from the death list -> gib qdels them -> gib returns to death(0) -> death(0) returns

Suggested change
new /obj/effect/gibspawner/generic(src.loc)
qdel(src)
gibs(loc)
..()


/mob/living/simple_animal/hostile/forgotten_beast/OpenFire(target)
if(!mybreath)
return ..()
if(prob(70))
BreathAttack(target)
return
if(!projectiletype)
return
..()

/mob/living/simple_animal/hostile/forgotten_beast/New(loc, var/datum/procedural_mobspawn/add_template)
appearance_flags |= PIXEL_SCALE
if(!add_template) //no template provided
if(!procgen_mob_datums.len) //if no pre-generated templates available...
add_template = new /datum/procedural_mobspawn/ //generating a new one will add it to the list automatically
else
add_template = pick(procgen_mob_datums)
template = add_template
meat_type = pick(typesof(/obj/item/weapon/reagent_containers/food/snacks/meat))

name = template.name
health = template.health
maxHealth = template.maxHealth
desc = template.desc
icon = template.icon
icon_state = template.icon_state
icon_dead = template.icon_dead
pixel_x = template.pixel_x
pixel_y = template.pixel_y
melee_damage_lower = template.melee_damage_lower
melee_damage_upper = template.melee_damage_upper
mybreath = template.mybreath
ranged = template.ranged
rapid = template.rapid
projectiletype = template.projectiletype
move_to_delay = template.move_to_delay
color = template.color
transform = template.size_matrix
radioactive = template.radioactive
vapors = template.vapors
loot = template.randomloot[1]
loot_count = template.randomloot[2]
..()

/mob/living/simple_animal/hostile/forgotten_beast/proc/BreathAttack(atom/A = target)
if(world.time < (special_cooldown + 10 SECONDS))
return
var/obj/item/projectile/custom_breath/thebreath = new /obj/item/projectile/custom_breath(src)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a weakref, otherwise you're going to get a hard del when the projectile deletes itself and has to remove this reference.

thebreath.name = mybreath.name//find a better way to do this
thebreath.damage = mybreath.damage
thebreath.color = mybreath.color
thebreath.damage_type = mybreath.damage_type
thebreath.pressure = mybreath.pressure
thebreath.temperature = mybreath.temperature
thebreath.special = mybreath.special
thebreath.reagent_type = mybreath.reagent_type
generic_projectile_fire(get_ranged_target_turf(src, dir, 10), src, thebreath, 'sound/weapons/flamethrower.ogg', src)
special_cooldown = world.time

/mob/living/simple_animal/hostile/forgotten_beast/proc/GasAttack()
playsound(get_turf(src), 'sound/effects/smoke.ogg', 50, FALSE, 8)
// Create the reagents to put into the air
reagents.add_reagent(vapors.id, 100)
var/datum/chemical_reaction/chemsmoke/CS = new()
CS.on_reaction(src.reagents)
pulse_cooldown = world.time

/mob/living/simple_animal/hostile/forgotten_beast/proc/rad_blast()//copied from glowing ones, does not require radiation
if(prob(30))
visible_message("<span class = 'blob'>\The [src] glows with a brilliant light!</span>")
set_light(vision_range/2, vision_range, "#a1d68b")
spawn(1 SECONDS)
emitted_harvestable_radiation(get_turf(src), rand(250, 500), range = 7)

for(var/mob/living/carbon/human/H in view(src, vision_range))
H.apply_radiation(15, RAD_EXTERNAL)
pulse_cooldown = world.time
spawn(3 SECONDS)
set_light(1, 2, "#5dca31")
Loading
Loading