mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-09 16:07:40 +00:00
This commit is contained in:
@@ -23,6 +23,7 @@ GLOBAL_LIST_EMPTY(antagonists)
|
||||
var/show_name_in_check_antagonists = FALSE //Will append antagonist name in admin listings - use for categories that share more than one antag type
|
||||
var/list/blacklisted_quirks = list(/datum/quirk/nonviolent,/datum/quirk/mute) // Quirks that will be removed upon gaining this antag. Pacifist and mute are default.
|
||||
var/threat = 0 // Amount of threat this antag poses, for dynamic mode
|
||||
var/show_to_ghosts = FALSE // Should this antagonist be shown as antag to ghosts? Shouldn't be used for stealthy antagonists like traitors
|
||||
|
||||
var/list/skill_modifiers
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
job_rank = ROLE_ABDUCTOR
|
||||
show_in_antagpanel = FALSE //should only show subtypes
|
||||
threat = 5
|
||||
show_to_ghosts = TRUE
|
||||
var/datum/team/abductor_team/team
|
||||
var/sub_role
|
||||
var/outfit
|
||||
@@ -32,6 +33,10 @@
|
||||
show_in_antagpanel = TRUE
|
||||
skill_modifiers = list(/datum/skill_modifier/job/affinity/surgery)
|
||||
|
||||
/datum/antagonist/abductor/scientist/onemanteam
|
||||
name = "Abductor Solo"
|
||||
outfit = /datum/outfit/abductor/scientist/onemanteam
|
||||
|
||||
/datum/antagonist/abductor/create_team(datum/team/abductor_team/new_team)
|
||||
if(!new_team)
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "Blob"
|
||||
roundend_category = "blobs"
|
||||
antagpanel_category = "Blob"
|
||||
show_to_ghosts = TRUE
|
||||
job_rank = ROLE_BLOB
|
||||
threat = 20
|
||||
var/datum/action/innate/blobpop/pop_action
|
||||
|
||||
@@ -92,6 +92,7 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
|
||||
//Don't delete upon mind destruction, otherwise soul re-selling will break.
|
||||
delete_on_mind_deletion = FALSE
|
||||
threat = 5
|
||||
show_to_ghosts = TRUE
|
||||
var/obligation
|
||||
var/ban
|
||||
var/bane
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "Sentient Disease"
|
||||
roundend_category = "diseases"
|
||||
antagpanel_category = "Disease"
|
||||
show_to_ghosts = TRUE
|
||||
var/disease_name = ""
|
||||
|
||||
/datum/antagonist/disease/on_gain()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
var/list/name_source
|
||||
threat = -5
|
||||
show_in_antagpanel = FALSE
|
||||
show_to_ghosts = TRUE
|
||||
antag_moodlet = /datum/mood_event/focused
|
||||
|
||||
/datum/antagonist/ert/on_gain()
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
roundend_category = "monkeys"
|
||||
antagpanel_category = "Monkey"
|
||||
threat = 3
|
||||
show_to_ghosts = TRUE
|
||||
var/datum/team/monkey/monkey_team
|
||||
var/monkey_only = TRUE
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
show_in_antagpanel = FALSE
|
||||
show_name_in_check_antagonists = TRUE
|
||||
threat = 5
|
||||
show_to_ghosts = TRUE
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
antagpanel_category = "Ninja"
|
||||
job_rank = ROLE_NINJA
|
||||
show_name_in_check_antagonists = TRUE
|
||||
show_to_ghosts = TRUE
|
||||
antag_moodlet = /datum/mood_event/focused
|
||||
threat = 8
|
||||
var/helping_station = FALSE
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
antag_moodlet = /datum/mood_event/focused
|
||||
threat = 10
|
||||
skill_modifiers = list(/datum/skill_modifier/job/level/wiring)
|
||||
show_to_ghosts = TRUE
|
||||
var/datum/team/nuclear/nuke_team
|
||||
var/always_new_team = FALSE //If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team.
|
||||
var/send_to_spawnpoint = TRUE //Should the user be moved to default spawnpoint.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
show_in_antagpanel = FALSE
|
||||
var/datum/objective/mission
|
||||
var/datum/team/ert/ert_team
|
||||
show_to_ghosts = TRUE
|
||||
|
||||
/datum/antagonist/official/greet()
|
||||
to_chat(owner, "<B><font size=3 color=red>You are a CentCom Official.</font></B>")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
roundend_category = "space pirates"
|
||||
antagpanel_category = "Pirate"
|
||||
threat = 5
|
||||
show_to_ghosts = TRUE
|
||||
var/datum/team/pirate/crew
|
||||
|
||||
/datum/antagonist/pirate/greet()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
show_in_antagpanel = FALSE
|
||||
show_name_in_check_antagonists = TRUE
|
||||
threat = 5
|
||||
show_to_ghosts = TRUE
|
||||
|
||||
/datum/antagonist/revenant/greet()
|
||||
owner.announce_objectives()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/datum/antagonist/santa
|
||||
name = "Santa"
|
||||
show_in_antagpanel = FALSE
|
||||
show_name_in_check_antagonists = TRUE
|
||||
show_to_ghosts = TRUE
|
||||
|
||||
/datum/antagonist/santa/on_gain()
|
||||
. = ..()
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
threat = 10
|
||||
job_rank = ROLE_ALIEN
|
||||
show_in_antagpanel = FALSE
|
||||
show_to_ghosts = TRUE
|
||||
|
||||
/datum/antagonist/slaughter/on_gain()
|
||||
forge_objectives()
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
var/move_to_lair = TRUE
|
||||
var/outfit_type = /datum/outfit/wizard
|
||||
var/wiz_age = WIZARD_AGE_MIN /* Wizards by nature cannot be too young. */
|
||||
show_to_ghosts = TRUE
|
||||
|
||||
/datum/antagonist/wizard/on_gain()
|
||||
register()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
name = "Xenomorph"
|
||||
job_rank = ROLE_ALIEN
|
||||
show_in_antagpanel = FALSE
|
||||
show_to_ghosts = TRUE
|
||||
var/datum/team/xeno/xeno_team
|
||||
threat = 3
|
||||
|
||||
|
||||
@@ -369,9 +369,12 @@
|
||||
"dna_extra.gif" = 'html/dna_extra.gif'
|
||||
)
|
||||
|
||||
/*
|
||||
/datum/asset/simple/orbit
|
||||
assets = list(
|
||||
"ghost.png" = 'html/ghost.png'
|
||||
)
|
||||
|
||||
assets = list(
|
||||
"ghost.png" = 'html/ghost.png'
|
||||
)
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
|
||||
// Used for displaying in ghost chat, without changing the actual name
|
||||
// of the mob
|
||||
var/deadchat_name
|
||||
var/datum/orbit_menu/orbit_menu
|
||||
var/datum/spawners_menu/spawners_menu
|
||||
|
||||
/mob/dead/observer/Initialize(mapload, mob/body)
|
||||
@@ -161,6 +162,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
|
||||
|
||||
updateallghostimages()
|
||||
|
||||
QDEL_NULL(orbit_menu)
|
||||
QDEL_NULL(spawners_menu)
|
||||
return ..()
|
||||
|
||||
@@ -490,10 +492,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
set name = "Orbit" // "Haunt"
|
||||
set desc = "Follow and orbit a mob."
|
||||
|
||||
var/list/mobs = getpois(skip_mindless=1)
|
||||
var/input = input("Please, select a mob!", "Haunt", null, null) as null|anything in mobs
|
||||
var/mob/target = mobs[input]
|
||||
ManualFollow(target)
|
||||
if(!orbit_menu)
|
||||
orbit_menu = new(src)
|
||||
|
||||
orbit_menu.ui_interact(src)
|
||||
|
||||
// This is the ghost's follow verb with an argument
|
||||
/mob/dead/observer/proc/ManualFollow(atom/movable/target)
|
||||
|
||||
78
code/modules/mob/dead/observer/orbit.dm
Normal file
78
code/modules/mob/dead/observer/orbit.dm
Normal file
@@ -0,0 +1,78 @@
|
||||
/datum/orbit_menu
|
||||
var/mob/dead/observer/owner
|
||||
|
||||
/datum/orbit_menu/New(mob/dead/observer/new_owner)
|
||||
if(!istype(new_owner))
|
||||
qdel(src)
|
||||
owner = new_owner
|
||||
|
||||
/datum/orbit_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state)
|
||||
if (!ui)
|
||||
ui = new(user, src, ui_key, "Orbit", "Orbit", 350, 700, master_ui, state)
|
||||
ui.open()
|
||||
|
||||
/datum/orbit_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
if (..())
|
||||
return
|
||||
|
||||
if (action == "orbit")
|
||||
var/list/pois = getpois(skip_mindless = 1)
|
||||
var/atom/movable/poi = pois[params["name"]]
|
||||
if (poi != null)
|
||||
owner.ManualFollow(poi)
|
||||
ui.close()
|
||||
|
||||
/datum/orbit_menu/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/alive = list()
|
||||
var/list/antagonists = list()
|
||||
var/list/dead = list()
|
||||
var/list/ghosts = list()
|
||||
var/list/misc = list()
|
||||
var/list/npcs = list()
|
||||
|
||||
var/list/pois = getpois(skip_mindless = 1)
|
||||
for (var/name in pois)
|
||||
var/list/serialized = list()
|
||||
serialized["name"] = name
|
||||
|
||||
var/poi = pois[name]
|
||||
|
||||
var/mob/M = poi
|
||||
if (istype(M))
|
||||
if (isobserver(M))
|
||||
ghosts += list(serialized)
|
||||
else if (M.stat == DEAD)
|
||||
dead += list(serialized)
|
||||
else if (M.mind == null)
|
||||
npcs += list(serialized)
|
||||
else
|
||||
var/number_of_orbiters = M.orbiters?.orbiters?.len
|
||||
if (number_of_orbiters)
|
||||
serialized["orbiters"] = number_of_orbiters
|
||||
|
||||
var/datum/mind/mind = M.mind
|
||||
var/was_antagonist = FALSE
|
||||
|
||||
for (var/_A in mind.antag_datums)
|
||||
var/datum/antagonist/A = _A
|
||||
if (A.show_to_ghosts)
|
||||
was_antagonist = TRUE
|
||||
serialized["antag"] = A.name
|
||||
antagonists += list(serialized)
|
||||
break
|
||||
|
||||
if (!was_antagonist)
|
||||
alive += list(serialized)
|
||||
else
|
||||
misc += list(serialized)
|
||||
|
||||
data["alive"] = alive
|
||||
data["antagonists"] = antagonists
|
||||
data["dead"] = dead
|
||||
data["ghosts"] = ghosts
|
||||
data["misc"] = misc
|
||||
data["npcs"] = npcs
|
||||
|
||||
return data
|
||||
BIN
html/ghost.png
Normal file
BIN
html/ghost.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 307 B |
@@ -2319,6 +2319,8 @@
|
||||
#include "code\modules\mob\dead\observer\observer.dm"
|
||||
#include "code\modules\mob\dead\observer\observer_movement.dm"
|
||||
#include "code\modules\mob\dead\observer\say.dm"
|
||||
#include "code\modules\mob\dead\observer\observer_say.dm"
|
||||
#include "code\modules\mob\dead\observer\orbit.dm"
|
||||
#include "code\modules\mob\living\blood.dm"
|
||||
#include "code\modules\mob\living\bloodcrawl.dm"
|
||||
#include "code\modules\mob\living\damage_procs.dm"
|
||||
|
||||
188
tgui/packages/tgui/interfaces/Orbit.js
Normal file
188
tgui/packages/tgui/interfaces/Orbit.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import { createSearch } from 'common/string';
|
||||
import { Box, Button, Input, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
|
||||
const PATTERN_DESCRIPTOR = / \[(?:ghost|dead)\]$/;
|
||||
const PATTERN_NUMBER = / \(([0-9]+)\)$/;
|
||||
|
||||
const searchFor = searchText => createSearch(searchText, thing => thing.name);
|
||||
|
||||
const compareString = (a, b) => a < b ? -1 : a > b;
|
||||
|
||||
const compareNumberedText = (a, b) => {
|
||||
const aName = a.name;
|
||||
const bName = b.name;
|
||||
|
||||
// Check if aName and bName are the same except for a number at the end
|
||||
// e.g. Medibot (2) and Medibot (3)
|
||||
const aNumberMatch = aName.match(PATTERN_NUMBER);
|
||||
const bNumberMatch = bName.match(PATTERN_NUMBER);
|
||||
|
||||
if (aNumberMatch
|
||||
&& bNumberMatch
|
||||
&& aName.replace(PATTERN_NUMBER, "") === bName.replace(PATTERN_NUMBER, "")
|
||||
) {
|
||||
const aNumber = parseInt(aNumberMatch[1], 10);
|
||||
const bNumber = parseInt(bNumberMatch[1], 10);
|
||||
|
||||
return aNumber - bNumber;
|
||||
}
|
||||
|
||||
return compareString(aName, bName);
|
||||
};
|
||||
|
||||
const BasicSection = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
const { searchText, source, title } = props;
|
||||
const things = source.filter(searchFor(searchText));
|
||||
things.sort(compareNumberedText);
|
||||
return source.length > 0 && (
|
||||
<Section title={title}>
|
||||
{things.map(thing => (
|
||||
<Button
|
||||
key={thing.name}
|
||||
content={thing.name.replace(PATTERN_DESCRIPTOR, "")}
|
||||
onClick={() => act("orbit", {
|
||||
name: thing.name,
|
||||
})} />
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const OrbitedButton = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
const { color, thing } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
color={color}
|
||||
onClick={() => act("orbit", {
|
||||
name: thing.name,
|
||||
})}>
|
||||
{thing.name}
|
||||
{thing.orbiters && (
|
||||
<Box inline ml={1}>
|
||||
{"("}{thing.orbiters}{" "}
|
||||
<Box
|
||||
as="img"
|
||||
src="ghost.png"
|
||||
opacity={0.7} />
|
||||
{")"}
|
||||
</Box>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const Orbit = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
alive,
|
||||
antagonists,
|
||||
dead,
|
||||
ghosts,
|
||||
misc,
|
||||
npcs,
|
||||
} = data;
|
||||
|
||||
const [searchText, setSearchText] = useLocalState(context, "searchText", "");
|
||||
|
||||
const collatedAntagonists = {};
|
||||
for (const antagonist of antagonists) {
|
||||
if (collatedAntagonists[antagonist.antag] === undefined) {
|
||||
collatedAntagonists[antagonist.antag] = [];
|
||||
}
|
||||
collatedAntagonists[antagonist.antag].push(antagonist);
|
||||
}
|
||||
|
||||
const sortedAntagonists = Object.entries(collatedAntagonists);
|
||||
sortedAntagonists.sort((a, b) => {
|
||||
return compareString(a[0], b[0]);
|
||||
});
|
||||
|
||||
const orbitMostRelevant = searchText => {
|
||||
for (const source of [
|
||||
sortedAntagonists.map(([_, antags]) => antags),
|
||||
alive, ghosts, dead, npcs, misc,
|
||||
]) {
|
||||
const member = source
|
||||
.filter(searchFor(searchText))
|
||||
.sort(compareNumberedText)[0];
|
||||
if (member !== undefined) {
|
||||
act("orbit", { name: member.name });
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Window>
|
||||
<Window.Content>
|
||||
<Section>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
onInput={(_, value) => setSearchText(value)}
|
||||
onEnter={(_, value) => orbitMostRelevant(value)} />
|
||||
</Section>
|
||||
|
||||
{antagonists.length > 0 && (
|
||||
<Section title="Ghost-Visible Antagonists">
|
||||
{sortedAntagonists.map(([name, antags]) => (
|
||||
<Section key={name} title={name} level={2}>
|
||||
{antags
|
||||
.filter(searchFor(searchText))
|
||||
.sort(compareNumberedText)
|
||||
.map(antag => (
|
||||
<OrbitedButton
|
||||
key={antag.name}
|
||||
color="bad"
|
||||
thing={antag}
|
||||
/>
|
||||
))}
|
||||
</Section>
|
||||
))}
|
||||
</Section>
|
||||
)}
|
||||
|
||||
<Section title="Alive">
|
||||
{alive
|
||||
.filter(searchFor(searchText))
|
||||
.sort(compareNumberedText)
|
||||
.map(thing => (
|
||||
<OrbitedButton
|
||||
key={thing.name}
|
||||
color="good"
|
||||
thing={thing} />
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<BasicSection
|
||||
title="Ghosts"
|
||||
source={ghosts}
|
||||
searchText={searchText}
|
||||
/>
|
||||
|
||||
<BasicSection
|
||||
title="Dead"
|
||||
source={dead}
|
||||
searchText={searchText}
|
||||
/>
|
||||
|
||||
<BasicSection
|
||||
title="NPCs"
|
||||
source={npcs}
|
||||
searchText={searchText}
|
||||
/>
|
||||
|
||||
<BasicSection
|
||||
title="Misc"
|
||||
source={misc}
|
||||
searchText={searchText}
|
||||
/>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user