Ape Uprising (#31609)

* Allow monkey-like species to craft

* Add signal for mobs entering vents

* Implement basic uplifted antag datum & nest

* Make phrasing nicer

* Allow primitive species to vary & add time requirements

* Add spawning functionality to nest

* Make primitives unable to be humanised a little less violently

* Fix linter complaints

* Fix typo which made the linter mad

* Add objectives and antag teams

* Make nests consider other nests for spawn limit

* Add greeting text

* Add guaranteed early spawns

* Allow deconstruction of nests & recovery of scrap

* Allow uplifted mobs to ventcrawl with small items

* Clarify nest spawning conditions on examine

* Tweak NPC numbers & AI

* Add obtain objective and give an extra objective

* Give thanks to the linter

* Change team objective & make the alert nicer

* Make event only spawn monkeys by default

* Add examine text explaining deconstruction

* Allow ghosts to see nest materials

* Handle stacks properly in nest deconstruction

* Add on-demand ghost spawn to reduce roll spam

* Add nest crafting

* Allow lesser mobs to use medical stacks

I don't believe this should affect anything else, as every other mob capable of holding these items is an advanced tool user.

* Add hint about crafting on examine

* Ensure only nest monkeys retaliate to nest damage

* Add uplifted primitive nests to the spawners menu
This commit is contained in:
unknownuser782
2026-05-07 13:03:30 +08:00
committed by GitHub
parent 85002cca6c
commit 13c3cc803c
30 changed files with 942 additions and 16 deletions
+5
View File
@@ -485,6 +485,11 @@
if(length(SSticker.mode.zombie_infected))
dat += check_role_table_mob("Pre-zombie infected", SSticker.mode.zombie_infected)
if(length(SSticker.mode.uplifted_primitives))
for(var/datum/species/species_path in SSticker.mode.uplifted_primitives)
var/minds = SSticker.mode.uplifted_primitives[species_path]
dat += check_role_table("Uplifted Primitives ([species_path::name])", minds)
if(length(GLOB.ts_spiderlist))
var/list/spider_minds = list()
for(var/mob/living/simple_animal/hostile/poison/terror_spider/S in GLOB.ts_spiderlist)
@@ -26,6 +26,18 @@
if(M.mind)
M.mind.antag_hud = null
/datum/atom_hud/antag/hidden/secondary/join_hud(mob/M, slave)
if(!istype(M))
CRASH("join_hud(): [M] ([M.type]) is not a mob!")
add_to_hud(M)
if(self_visible)
add_hud_to(M)
/datum/atom_hud/antag/hidden/secondary/leave_hud(mob/M)
if(!istype(M))
CRASH("leave_hud(): [M] ([M.type]) is not a mob!")
remove_from_hud(M)
remove_hud_from(M)
//GAME_MODE PROCS
//called to set a mob's antag icon state
@@ -0,0 +1,160 @@
RESTRICT_TYPE(/datum/antagonist/uplifted_primitive)
/datum/antagonist/uplifted_primitive
name = "Uplifted Primitive"
job_rank = ROLE_UPLIFTED_PRIMITIVE
special_role = SPECIAL_ROLE_UPLIFTED_PRIMITIVE
antag_hud_name = "huduplifted"
antag_hud_type = ANTAG_HUD_UPLIFTED_TEAMLESS
// TODO: wiki page
//wiki_page_name = "Uplifted_Primitive"
/// The UID of the nest built by the owner.
var/nest_uid
/// The species of the owner at the time this datum was added.
var/datum/species/initial_species
/// The list of objective types which can be selected from when picking a personal objective.
var/list/potential_objectives = list(
/datum/objective/uplifted/collect_items,
/datum/objective/uplifted/collect_animals,
/datum/objective/uplifted/barter,
/datum/objective/uplifted/fortify,
/datum/objective/uplifted/obtain,
)
/datum/antagonist/uplifted_primitive/greet()
. = ..()
. += SPAN_BOLDNOTICE("Despite your primitive form, you have enlightened with sentience.")
. += SPAN_BOLDNOTICE("With this newfound awareness you are able to spread your species by building a nest and supplying it with food and scrap metal.")
. += SPAN_BOLDNOTICE("By propagating your kind and working with your fellow primitives, you may finally be able to defend yourself from the crew of the station.")
. += SPAN_BOLDNOTICE("Alternatively, you could try to keep a friendly relationship with the crew, though they may see you as a threat not worth keeping alive.")
. += SPAN_BOLDNOTICE("Regardless of the method, you must help your species to survive and prosper on the station.")
/datum/antagonist/uplifted_primitive/give_objectives()
add_antag_objective(pick_n_take(potential_objectives))
add_antag_objective(pick_n_take(potential_objectives))
/datum/antagonist/uplifted_primitive/apply_innate_effects(mob/living/mob_override)
var/mob/living/carbon/human/H = ..()
if(!istype(H))
return
if(H.dna.species.is_small)
ADD_TRAIT(H, TRAIT_GENELESS, UNIQUE_TRAIT_SOURCE(src))
H.ventcrawler = VENTCRAWLER_SIGNAL
RegisterSignal(H, COMSIG_LIVING_TRY_VENTCRAWL, PROC_REF(attempt_ventcrawl))
RegisterSignal(H, COMSIG_LIVING_ENTER_VENTCRAWL, PROC_REF(apply_ventcrawl_effects))
RegisterSignal(H, COMSIG_LIVING_EXIT_VENTCRAWL, PROC_REF(remove_ventcrawl_effects))
owner.AddSpell(new /datum/spell/uplifted_make_nest)
/datum/antagonist/uplifted_primitive/remove_innate_effects(mob/living/mob_override)
var/mob/living/carbon/human/H = ..()
if(!istype(H))
return
REMOVE_TRAIT(H, TRAIT_GENELESS, UNIQUE_TRAIT_SOURCE(src))
H.ventcrawler = initial(H.ventcrawler)
UnregisterSignal(H, COMSIG_LIVING_TRY_VENTCRAWL)
UnregisterSignal(H, COMSIG_LIVING_ENTER_VENTCRAWL)
UnregisterSignal(H, COMSIG_LIVING_EXIT_VENTCRAWL)
owner.RemoveSpell(/datum/spell/uplifted_make_nest)
/datum/antagonist/uplifted_primitive/proc/attempt_ventcrawl()
SIGNAL_HANDLER
var/mob/living/L = owner.current
for(var/obj/item/I in L.contents)
if(istype(I, /obj/item/bio_chip))
continue
if(istype(I, /obj/item/reagent_containers/patch))
continue
if(I.flags & ABSTRACT)
continue
if(I.w_class > WEIGHT_CLASS_NORMAL)
to_chat(L, SPAN_WARNING("You cannot crawl into a vent with large items!"))
return FALSE
return TRUE
/datum/antagonist/uplifted_primitive/proc/apply_ventcrawl_effects()
SIGNAL_HANDLER
var/mob/living/L = owner.current
ADD_TRAIT(L, TRAIT_RESISTLOWPRESSURE, UNIQUE_TRAIT_SOURCE(src))
ADD_TRAIT(L, TRAIT_RESISTHIGHPRESSURE, UNIQUE_TRAIT_SOURCE(src))
/datum/antagonist/uplifted_primitive/proc/remove_ventcrawl_effects()
SIGNAL_HANDLER
var/mob/living/L = owner.current
REMOVE_TRAIT(L, TRAIT_RESISTLOWPRESSURE, UNIQUE_TRAIT_SOURCE(src))
REMOVE_TRAIT(L, TRAIT_RESISTHIGHPRESSURE, UNIQUE_TRAIT_SOURCE(src))
/datum/antagonist/uplifted_primitive/add_antag_hud(mob/living/antag_mob)
var/datum/team/uplifted_primitive/team = get_team()
var/datum/atom_hud/antag/global_hud = GLOB.huds[antag_hud_type]
global_hud.join_hud(antag_mob)
if(team)
team.team_hud.join_hud(antag_mob)
set_antag_hud(antag_mob, antag_hud_name)
/datum/antagonist/uplifted_primitive/remove_antag_hud(mob/living/antag_mob)
var/datum/team/uplifted_primitive/team = get_team()
var/datum/atom_hud/antag/global_hud = GLOB.huds[antag_hud_type]
global_hud.leave_hud(antag_mob)
if(team)
team.team_hud.leave_hud(antag_mob)
set_antag_hud(antag_mob, null)
/datum/antagonist/uplifted_primitive/create_team(datum/team/team)
var/datum/species/owner_species = get_owner_species()
if(!owner_species)
return null
var/datum/team/uplifted_primitive/existing = SSticker.mode.uplifted_teams[owner_species]
if(!existing)
existing = new /datum/team/uplifted_primitive(new_species = owner_species)
return existing
/datum/antagonist/uplifted_primitive/get_team()
var/datum/species/owner_species = get_owner_species()
if(!owner_species)
return null
return SSticker.mode.uplifted_teams[owner_species]
/datum/antagonist/uplifted_primitive/add_owner_to_gamemode()
var/datum/species/owner_species = get_owner_species()
var/list/minds = SSticker.mode.uplifted_primitives[owner_species]
if(!minds)
minds = list()
SSticker.mode.uplifted_primitives[owner_species] = minds
minds |= owner
/datum/antagonist/uplifted_primitive/remove_owner_from_gamemode()
var/datum/species/owner_species = get_owner_species()
var/list/minds = SSticker.mode.uplifted_primitives[owner_species]
if(minds)
minds -= owner
if(!length(minds))
SSticker.mode.uplifted_primitives[owner_species] = null
SSticker.mode.uplifted_primitives -= owner_species
/datum/antagonist/uplifted_primitive/proc/get_owner_species()
if(initial_species == null)
var/mob/living/carbon/human/H = owner.current
if(istype(H))
initial_species = H.dna.species.type
else
initial_species = FALSE
return initial_species
@@ -0,0 +1,77 @@
RESTRICT_TYPE(/datum/team/uplifted_primitive)
/// The number of players a team must have before receiving their team objective.
#define TEAM_OBJECTIVE_MEMBER_THRESHOLD 3
/// An team containing all uplifted primitives of a given species.
/datum/team/uplifted_primitive
name = "Uplifted Primitives"
/// The species this team represents.
var/datum/species/team_species
/// The antag hud specific to this team, only shown to other members of the team.
var/datum/atom_hud/antag/team_hud = new()
/// Whether or not the team objective has been added yet.
var/has_added_team_objective = FALSE
/// All potential objectives which can be added as the team objective.
var/list/potential_objectives = list(
/datum/objective/uplifted/expand,
)
/// The number of spawns that should always poll ghosts for nests of this team.
var/guaranteed_sentient_spawns = 0
/datum/team/uplifted_primitive/New(list/starting_members, datum/species/new_species = /datum/species/monkey)
team_species = new_species
return ..(starting_members)
/datum/team/uplifted_primitive/create_team(list/starting_members)
. = ..()
name = "[team_species::name] Uprising"
add_team_objective(/datum/objective/uplifted/propagate)
add_team_objective(/datum/objective/uplifted/build_nest_in_area)
/datum/team/uplifted_primitive/can_create_team()
return SSticker.mode.uplifted_teams[team_species] == null
/datum/team/uplifted_primitive/assign_team(list/starting_members)
SSticker.mode.uplifted_teams[team_species] = src
/datum/team/uplifted_primitive/clear_team_reference()
SSticker.mode.uplifted_teams[team_species] = null
SSticker.mode.uplifted_teams -= team_species
/datum/team/uplifted_primitive/handle_adding_member(datum/mind/new_member)
..()
if(!has_added_team_objective && length(members) >= TEAM_OBJECTIVE_MEMBER_THRESHOLD)
has_added_team_objective = TRUE
addtimer(CALLBACK(src, PROC_REF(add_new_objective)), 10 SECONDS)
/datum/team/uplifted_primitive/proc/add_new_objective()
var/selected_objective_path = pick_n_take(potential_objectives)
var/datum/objective/new_objective = new selected_objective_path()
add_team_objective(new_objective)
announce_new_objective(members, new_objective)
/datum/team/uplifted_primitive/proc/announce_new_objective(list/announce_to, datum/objective/new_objective)
var/message = prepare_new_objective_message(new_objective)
for(var/datum/mind/member in announce_to)
if(!member.current || !isliving(member.current))
continue
to_chat(member.current, message)
SEND_SOUND(member.current, sound('sound/ambience/alarm4.ogg'))
/datum/team/uplifted_primitive/proc/prepare_new_objective_message(datum/objective/new_objective)
var/list/messages = list()
messages += SPAN_USERDANGER("Your species grows!")
messages += SPAN_NOTICE("With your numbers rising, you devise a new objective:")
messages += new_objective.explanation_text
return chat_box_red(messages.Join("<br>"))
#undef TEAM_OBJECTIVE_MEMBER_THRESHOLD
@@ -12,6 +12,7 @@ GLOBAL_LIST_INIT(special_role_times, list(
ROLE_VAMPIRE = 14,
ROLE_BLOB = 14,
ROLE_REVENANT = 14,
ROLE_UPLIFTED_PRIMITIVE = 14,
ROLE_OPERATIVE = 21,
ROLE_CULTIST = 21,
ROLE_ALIEN = 21,
+1
View File
@@ -222,6 +222,7 @@ GLOBAL_LIST_EMPTY(event_last_fired)
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/space_ninja, 9),
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/wizard_adept, 5),
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/spawn_morph, 16, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/spawn_uplifted_primitive, 16, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/aurora_caelus, 5, is_one_shot = TRUE),
//new /datum/event_meta(EVENT_LEVEL_MAJOR, /datum/event/spawn_pulsedemon, 20, is_one_shot = TRUE)
)
@@ -580,7 +580,10 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, list(/obj/machinery/atmospherics/unary/ven
to_chat(src, SPAN_WARNING("You cannot crawl into a vent while buckled to something!"))
return
if(iscarbon(src) && length(contents) && ventcrawlerlocal < VENTCRAWLER_ALWAYS) // If we're here you can only ventcrawl while completely nude
if(ventcrawlerlocal == VENTCRAWLER_SIGNAL && !SEND_SIGNAL(src, COMSIG_LIVING_TRY_VENTCRAWL))
return
if(ventcrawlerlocal == VENTCRAWLER_NUDE && iscarbon(src) && length(contents)) // If we're here you can only ventcrawl while completely nude
for(var/obj/item/I in contents)
if(istype(I, /obj/item/bio_chip))
continue
@@ -602,7 +605,7 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, list(/obj/machinery/atmospherics/unary/ven
var/datum/pipeline/pipenet = starting_machine.returnPipenet(target_move)
pipenet.add_ventcrawler(src)
add_ventcrawl_images(pipenet)
SEND_SIGNAL(src, COMSIG_LIVING_ENTER_VENTCRAWL)
/mob/living/proc/add_ventcrawl_images(datum/pipeline/pipenet)
var/list/totalMembers = list()
@@ -96,7 +96,6 @@
var/meat_type = /obj/item/food/meat
var/no_equip // bitflags of slots the race can't equip stuff to
var/nojumpsuit = 0 // this is sorta... weird. it basically lets you equip stuff that usually needs jumpsuits without one, like belts and pockets and ids
var/can_craft = TRUE // Can this mob using crafting or not?
var/bodyflags = 0
var/dietflags = 0 // Make sure you set this, otherwise it won't be able to digest a lot of foods
@@ -971,7 +970,7 @@
if(radiation > RAD_MOB_VOMIT && prob(RAD_MOB_VOMIT_PROB))
H.vomit(10, TRUE)
if(radiation > RAD_MOB_MUTATE)
if(radiation > RAD_MOB_MUTATE && !HAS_TRAIT(H, TRAIT_GENELESS))
if(prob(1))
to_chat(H, SPAN_DANGER("You mutate!"))
randmutb(H)
@@ -15,7 +15,6 @@
greater_form = /datum/species/human
no_equip = ITEM_SLOT_BELT | ITEM_SLOT_ID | ITEM_SLOT_LEFT_EAR | ITEM_SLOT_RIGHT_EAR | ITEM_SLOT_EYES | ITEM_SLOT_GLOVES | ITEM_SLOT_SHOES | ITEM_SLOT_OUTER_SUIT | ITEM_SLOT_JUMPSUIT | ITEM_SLOT_LEFT_POCKET | ITEM_SLOT_RIGHT_POCKET | ITEM_SLOT_SUIT_STORE | ITEM_SLOT_PDA | ITEM_SLOT_NECK
inherent_factions = list("jungle", "monkey")
can_craft = FALSE
is_small = 1
has_fine_manipulation = 0
ventcrawler = VENTCRAWLER_NUDE