mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-11 07:58:57 +01:00
124f7ee17b
* Please describe the intent of your changes in a clear fashion. This PR addresses a server crash caused by an invalid icon state being requested for certain plants, specifically those with spreading traits like biomass vines. **Root Cause:** The `refresh_icon()` proc for `/obj/effect/plant` was directly decrementing the instance variable `max_growth` based on fringe conditions (`spread_distance` and `at_fringe`). This meant that `max_growth` could become negative over multiple processing ticks. When `seed.get_icon()` was subsequently called with a negative growth stage (e.g., -2), it constructed an invalid icon state string (e.g., `biomass--2`), leading to a crash. **Solution:** To prevent `max_growth` from being permanently altered, a local variable `effective_max` is now used within `refresh_icon()`. This local variable is initialized with the current `max_growth` and then decremented based on the fringe conditions. The `growth` calculation and subsequent icon state generation now use `effective_max`, ensuring that the persistent `max_growth` variable remains stable and positive, thus preventing the generation of invalid negative icon states. * Please make sure that, in the case of mapping changes, you include images of these changes in the PR's description. * Please make sure to mark your PR as wip or review required by making a comment with !wip or !review required * If you include sprites/sounds/... (assets) that you have not created yourself specify the license and original author below. * Ensure that you also credit them in the appropriate location / changelog as specified in the contributor guidelines ### Asset Licenses The following assets that **have not** been created by myself are included in this PR: | Path | Original Author | License | | --- | --- | --- | | icons/example.dmi | ExamplePerson (Example Station) | CC0 | Fixes SERVER-PROD-TJ --------- Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com>
331 lines
8.8 KiB
Plaintext
331 lines
8.8 KiB
Plaintext
#define DEFAULT_SEED "glowshroom"
|
|
#define VINE_GROWTH_STAGES 5
|
|
|
|
/proc/spacevine_infestation(var/potency_min=85, var/potency_max=100, var/maturation_min=1, var/maturation_max=3)
|
|
/// List of areas to affect.
|
|
var/list/area/areaList = list()
|
|
/// Area types to include.
|
|
var/list/areaType = list(
|
|
/area/horizon/hallway,
|
|
/area/horizon/security/hallway,
|
|
/area/horizon/engineering/hallway,
|
|
/area/horizon/medical/hallway,
|
|
/area/horizon/operations/lobby,
|
|
/area/horizon/rnd/hallway,
|
|
/area/horizon/security/hallway,
|
|
/area/horizon/security/investigations_hallway,
|
|
/area/horizon/service/cafeteria,
|
|
/area/horizon/hangar
|
|
)
|
|
for(var/area/area in GLOB.the_station_areas)
|
|
if(is_type_in_list(area, areaType))
|
|
areaList += area
|
|
|
|
var/turf/T = pick_subarea_turf(pick(areaList), list(/proc/is_station_turf, /proc/not_turf_contains_dense_objects))
|
|
if(T)
|
|
var/datum/seed/seed = SSplants.create_random_seed(TRUE, SEED_NOUN_PITS)
|
|
SET_SEED_TRAIT(seed, TRAIT_SPREAD, 2) // So it will function properly as vines.
|
|
seed.growth_stages = VINE_GROWTH_STAGES
|
|
SET_SEED_TRAIT(seed, TRAIT_POTENCY, rand(potency_min, potency_max)) // 85-100 potency will help guarantee a wide spread and powerful effects.
|
|
SET_SEED_TRAIT(seed, TRAIT_MATURATION, rand(maturation_min, maturation_max))
|
|
|
|
//make vine zero start off fully matured
|
|
var/obj/effect/plant/vine = new(T,seed)
|
|
vine.health = vine.maxhealth
|
|
vine.mature_time = 0
|
|
vine.process()
|
|
var/area_display_name = get_area_display_name(get_area(T))
|
|
|
|
log_and_message_admins("Spacevines spawned at \the [area_display_name]", location = T)
|
|
return
|
|
|
|
log_and_message_admins(SPAN_NOTICE("Event: Spacevines failed to find a viable turf."))
|
|
|
|
/obj/effect/dead_plant
|
|
anchored = 1
|
|
opacity = 0
|
|
density = 0
|
|
color = DEAD_PLANT_COLOUR
|
|
|
|
/obj/effect/dead_plant/attack_hand()
|
|
qdel(src)
|
|
|
|
/obj/effect/dead_plant/attackby()
|
|
..()
|
|
for(var/obj/effect/plant/neighbor in range(1))
|
|
neighbor.update_neighbors()
|
|
qdel(src)
|
|
|
|
/obj/effect/plant
|
|
name = "plant"
|
|
anchored = 1
|
|
opacity = 0
|
|
density = 0
|
|
icon = 'icons/obj/hydroponics_growing.dmi'
|
|
icon_state = ""
|
|
layer = 3
|
|
movable_flags = MOVABLE_FLAG_PROXMOVE
|
|
pass_flags = PASSTABLE
|
|
mouse_opacity = MOUSE_OPACITY_OPAQUE
|
|
maxhealth = 100
|
|
var/growth_threshold = 0
|
|
var/growth_type = 0
|
|
|
|
///The maximum growth of this plant effect, aka the maximum stage
|
|
var/max_growth = 0
|
|
var/list/neighbors = list()
|
|
var/obj/effect/plant/parent
|
|
var/datum/seed/seed
|
|
var/sampled = 0
|
|
var/floor = 0
|
|
var/spread_chance = 40
|
|
var/spread_distance = 3
|
|
var/evolve_chance = 2
|
|
var/mature_time //minimum maturation time
|
|
var/last_tick = 0
|
|
var/obj/structure/machinery/portable_atmospherics/hydroponics/soil/invisible/plant
|
|
var/last_biolum = null
|
|
|
|
/obj/effect/plant/Destroy()
|
|
SSplants.remove_plant(src)
|
|
for(var/obj/effect/plant/neighbor in range(1,src))
|
|
if (!QDELETED(neighbor))
|
|
SSplants.add_plant(neighbor)
|
|
QDEL_NULL(plant)
|
|
return ..()
|
|
|
|
/obj/effect/plant/single
|
|
spread_chance = 0
|
|
|
|
/obj/effect/plant/Initialize(mapload, datum/seed/newseed, obj/effect/plant/newparent)
|
|
. = ..()
|
|
|
|
if(!newparent)
|
|
parent = src
|
|
else
|
|
parent = newparent
|
|
|
|
if(!SSplants)
|
|
to_world(SPAN_DANGER("Plant controller does not exist and [src] requires it. Aborting."))
|
|
qdel(src)
|
|
return
|
|
|
|
if(!istype(newseed))
|
|
newseed = SSplants.seeds[DEFAULT_SEED]
|
|
seed = newseed
|
|
if(!seed)
|
|
qdel(src)
|
|
return
|
|
|
|
name = seed.display_name
|
|
maxhealth = round(GET_SEED_TRAIT(seed, TRAIT_ENDURANCE)/2)
|
|
if(GET_SEED_TRAIT(seed, TRAIT_SPREAD) == 2)
|
|
mouse_opacity = 2
|
|
max_growth = VINE_GROWTH_STAGES
|
|
growth_threshold = maxhealth/VINE_GROWTH_STAGES
|
|
growth_type = seed.get_growth_type()
|
|
else
|
|
max_growth = seed.growth_stages
|
|
growth_threshold = maxhealth/seed.growth_stages
|
|
|
|
if(max_growth > 2 && prob(50))
|
|
max_growth-- //Ensure some variation in final sprite, makes the carpet of crap look less wonky.
|
|
|
|
can_buckle = list(/mob/living)
|
|
|
|
mature_time = world.time + GET_SEED_TRAIT(seed, TRAIT_MATURATION) + 15 //prevent vines from maturing until at least a few seconds after they've been created.
|
|
spread_chance = GET_SEED_TRAIT(seed, TRAIT_POTENCY)
|
|
spread_distance = (growth_type ? round(spread_chance * 0.6) : round(spread_chance * 0.3))
|
|
update_icon()
|
|
return INITIALIZE_HINT_LATELOAD
|
|
|
|
// Plants will sometimes be spawned in the turf adjacent to the one they need to end up in, for the sake of correct dir/etc being set.
|
|
/obj/effect/plant/LateInitialize()
|
|
. = ..()
|
|
set_dir(calc_dir())
|
|
update_icon()
|
|
SSplants.add_plant(src)
|
|
// Some plants eat through plating.
|
|
if(islist(seed.chems) && !isnull(seed.chems[/singleton/reagent/acid/polyacid]))
|
|
var/turf/T = get_turf(src)
|
|
T.ex_act(prob(80) ? 3 : 2)
|
|
|
|
/obj/effect/plant/update_icon()
|
|
//TODO: should really be caching this.
|
|
refresh_icon()
|
|
if(!growth_type && !floor)
|
|
src.transform = null
|
|
var/matrix/M = matrix()
|
|
// should make the plant flush against the wall it's meant to be growing from.
|
|
M.Translate(0,-(rand(12,14)))
|
|
switch(dir)
|
|
if(WEST)
|
|
M.Turn(90)
|
|
if(NORTH)
|
|
M.Turn(180)
|
|
if(EAST)
|
|
M.Turn(270)
|
|
src.transform = M
|
|
var/icon_colour = GET_SEED_TRAIT(seed, TRAIT_PLANT_COLOUR)
|
|
if(icon_colour)
|
|
color = icon_colour
|
|
// Apply colour and light from seed datum.
|
|
if(GET_SEED_TRAIT(seed, TRAIT_BIOLUM))
|
|
var/clr
|
|
if(GET_SEED_TRAIT(seed, TRAIT_BIOLUM_COLOUR))
|
|
clr = GET_SEED_TRAIT(seed, TRAIT_BIOLUM_COLOUR)
|
|
var/val = 1+round(GET_SEED_TRAIT(seed, TRAIT_POTENCY)/20)
|
|
if (val != last_biolum)
|
|
last_biolum = val
|
|
set_light(val, l_color = clr)
|
|
return
|
|
else
|
|
if (last_biolum)
|
|
set_light(0)
|
|
last_biolum = null
|
|
|
|
/obj/effect/plant/proc/refresh_icon()
|
|
SHOULD_NOT_SLEEP(TRUE)
|
|
|
|
overlays.Cut()
|
|
var/effective_max = max_growth
|
|
var/at_fringe = get_dist(src,parent)
|
|
if(spread_distance > 5)
|
|
if(at_fringe >= (spread_distance-3))
|
|
effective_max--
|
|
if(at_fringe >= (spread_distance-2))
|
|
effective_max--
|
|
|
|
var/growth = 0
|
|
if(growth_threshold)
|
|
growth = min(effective_max, round(health/growth_threshold))
|
|
|
|
var/image/our_icon = seed.get_icon(growth)
|
|
|
|
if(!istype(our_icon))
|
|
crash_with("The plant didn't return an icon!")
|
|
|
|
AddOverlays(our_icon)
|
|
|
|
if(growth>2 && growth == effective_max)
|
|
layer = (seed && seed.force_layer) ? seed.force_layer : 5
|
|
if(growth_type in list(GROWTH_VINES,GROWTH_BIOMASS))
|
|
opacity = 1
|
|
if(islist(seed.chems) && !isnull(seed.chems[/singleton/reagent/woodpulp]))
|
|
opacity = 1
|
|
density = 1
|
|
if(GET_SEED_TRAIT(seed, TRAIT_LARGE))
|
|
density = 1
|
|
opacity = 1
|
|
else
|
|
layer = (seed && seed.force_layer) ? seed.force_layer : 5
|
|
density = 0
|
|
|
|
/obj/effect/plant/proc/calc_dir()
|
|
var/turf/T = get_turf(src)
|
|
if(!istype(T))
|
|
return
|
|
|
|
var/direction = 16
|
|
|
|
for(var/wallDir in GLOB.cardinals)
|
|
var/turf/newTurf = get_step(T,wallDir)
|
|
if(newTurf.density)
|
|
direction |= wallDir
|
|
|
|
for(var/obj/effect/plant/shroom in T.contents)
|
|
if(shroom == src)
|
|
continue
|
|
if(shroom.floor) //special
|
|
direction &= ~16
|
|
else
|
|
direction &= ~shroom.dir
|
|
|
|
var/list/dirList = list()
|
|
|
|
for(var/i=1,i<=16,i <<= 1)
|
|
if(direction & i)
|
|
dirList += i
|
|
|
|
if(dirList.len)
|
|
var/newDir = pick(dirList)
|
|
if(newDir == 16)
|
|
floor = 1
|
|
newDir = 1
|
|
return newDir
|
|
|
|
floor = 1
|
|
return 1
|
|
|
|
/obj/effect/plant/attackby(obj/item/attacking_item, mob/user)
|
|
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
|
|
user.do_attack_animation(src)
|
|
SSplants.add_plant(src)
|
|
|
|
if(attacking_item.tool_behaviour == TOOL_WIRECUTTER || istype(attacking_item, /obj/item/surgery/scalpel))
|
|
if(sampled)
|
|
to_chat(user, SPAN_WARNING("\The [src] has already been sampled recently."))
|
|
return
|
|
if(!is_mature())
|
|
to_chat(user, SPAN_WARNING("\The [src] is not mature enough to yield a sample yet."))
|
|
return
|
|
if(!seed)
|
|
to_chat(user, SPAN_WARNING("There is nothing to take a sample from."))
|
|
return
|
|
if(sampled)
|
|
to_chat(user, SPAN_DANGER("You cannot take another sample from \the [src]."))
|
|
return
|
|
if(prob(70))
|
|
sampled = 1
|
|
seed.harvest(user,0,1)
|
|
health -= (rand(3,5)*5)
|
|
sampled = 1
|
|
else
|
|
playsound(loc, SFX_BREAK_WOOD, 50, TRUE)
|
|
var/damage = attacking_item.force ? attacking_item.force : 1 //always do at least a little damage
|
|
if(attacking_item.edge || attacking_item.sharp)
|
|
damage *= 2
|
|
health -= damage
|
|
check_health()
|
|
|
|
/obj/effect/plant/attack_hand(user)
|
|
if(!ishuman(user))
|
|
return FALSE
|
|
|
|
manual_unbuckle(user)
|
|
|
|
var/mob/living/carbon/human/H = user
|
|
playsound(loc, SFX_BREAK_WOOD, 50, TRUE)
|
|
var/damage = H.default_attack.get_unarmed_damage(H, src) ? H.default_attack.get_unarmed_damage(H, src) : 1
|
|
if(H.default_attack.edge || H.default_attack.sharp)
|
|
damage *= 2
|
|
health -= damage
|
|
check_health()
|
|
|
|
/obj/effect/plant/ex_act(severity)
|
|
switch(severity)
|
|
if(1.0)
|
|
die_off()
|
|
return
|
|
if(2.0)
|
|
if (prob(50))
|
|
die_off()
|
|
return
|
|
if(3.0)
|
|
if (prob(5))
|
|
die_off()
|
|
return
|
|
|
|
return
|
|
|
|
/obj/effect/plant/proc/check_health()
|
|
if(health <= 0)
|
|
die_off()
|
|
|
|
/obj/effect/plant/proc/is_mature()
|
|
return (health >= (maxhealth/3) && world.time > mature_time)
|
|
|
|
|
|
#undef DEFAULT_SEED
|
|
#undef VINE_GROWTH_STAGES
|