Merge branch 'master' into void-but-for-real
This commit is contained in:
@@ -54,24 +54,7 @@
|
||||
output += "<p>[LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]</p>"
|
||||
|
||||
if(!IsGuestKey(src.key))
|
||||
if (SSdbcore.Connect())
|
||||
var/isadmin = 0
|
||||
if(src.client && src.client.holder)
|
||||
isadmin = 1
|
||||
var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")")
|
||||
var/rs = REF(src)
|
||||
if(query_get_new_polls.Execute())
|
||||
var/newpoll = 0
|
||||
if(query_get_new_polls.NextRow())
|
||||
newpoll = 1
|
||||
|
||||
if(newpoll)
|
||||
output += "<p><b><a href='byond://?src=[rs];showpoll=1'>Show Player Polls</A> (NEW!)</b></p>"
|
||||
else
|
||||
output += "<p><a href='byond://?src=[rs];showpoll=1'>Show Player Polls</A></p>"
|
||||
qdel(query_get_new_polls)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
output += playerpolls()
|
||||
|
||||
output += "</center>"
|
||||
|
||||
@@ -81,6 +64,41 @@
|
||||
popup.set_content(output)
|
||||
popup.open(FALSE)
|
||||
|
||||
/mob/dead/new_player/proc/playerpolls()
|
||||
var/output = "" //hey tg why is this a list?
|
||||
if (SSdbcore.Connect())
|
||||
var/isadmin = FALSE
|
||||
if(client?.holder)
|
||||
isadmin = TRUE
|
||||
var/datum/db_query/query_get_new_polls = SSdbcore.NewQuery({"
|
||||
SELECT id FROM [format_table_name("poll_question")]
|
||||
WHERE (adminonly = 0 OR :isadmin = 1)
|
||||
AND Now() BETWEEN starttime AND endtime
|
||||
AND deleted = 0
|
||||
AND id NOT IN (
|
||||
SELECT pollid FROM [format_table_name("poll_vote")]
|
||||
WHERE ckey = :ckey
|
||||
AND deleted = 0
|
||||
)
|
||||
AND id NOT IN (
|
||||
SELECT pollid FROM [format_table_name("poll_textreply")]
|
||||
WHERE ckey = :ckey
|
||||
AND deleted = 0
|
||||
)
|
||||
"}, list("isadmin" = isadmin, "ckey" = ckey))
|
||||
var/rs = REF(src)
|
||||
if(!query_get_new_polls.Execute())
|
||||
qdel(query_get_new_polls)
|
||||
return
|
||||
if(query_get_new_polls.NextRow())
|
||||
output += "<p><b><a href='byond://?src=[rs];showpoll=1'>Show Player Polls</A> (NEW!)</b></p>"
|
||||
else
|
||||
output += "<p><a href='byond://?src=[rs];showpoll=1'>Show Player Polls</A></p>"
|
||||
qdel(query_get_new_polls)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
return output
|
||||
|
||||
/mob/dead/new_player/proc/age_gate()
|
||||
var/list/dat = list("<center>")
|
||||
dat += "Enter your date of birth here, to confirm that you are over 18.<BR>"
|
||||
@@ -123,71 +141,76 @@
|
||||
if(!client.set_db_player_flags())
|
||||
message_admins("Blocked [src] from new player panel because age gate could not access player database flags.")
|
||||
return FALSE
|
||||
else
|
||||
var/dbflags = client.prefs.db_flags
|
||||
if(dbflags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE) //they have not completed age gate
|
||||
var/age_verification = age_gate()
|
||||
if(age_verification != 1)
|
||||
client.add_system_note("Automated-Age-Gate", "Failed automatic age gate process.")
|
||||
//ban them and kick them
|
||||
|
||||
//parameters used by sql line, easier to read:
|
||||
var/bantype_str = "ADMIN_PERMABAN"
|
||||
var/reason = "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification."
|
||||
var/duration = -1
|
||||
var/sql_ckey = sanitizeSQL(client.ckey)
|
||||
var/computerid = client.computer_id
|
||||
if(!computerid)
|
||||
computerid = "0"
|
||||
var/sql_computerid = sanitizeSQL(computerid)
|
||||
var/ip = client.address
|
||||
if(!ip)
|
||||
ip = "0.0.0.0"
|
||||
var/sql_ip = sanitizeSQL(ip)
|
||||
if(!(client.prefs.db_flags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE)) //completed? Skip
|
||||
return TRUE
|
||||
|
||||
//parameter not used as there's no job but i want to fill out all parameters for the insert line
|
||||
var/sql_job
|
||||
var/age_verification = age_gate()
|
||||
//ban them and kick them
|
||||
if(age_verification != 1)
|
||||
// this isn't code, this is paragraphs.
|
||||
var/player_ckey = ckey(client.ckey)
|
||||
// record all admins and non-admins online at the time
|
||||
var/list/clients_online = GLOB.clients.Copy()
|
||||
var/list/admins_online = GLOB.admins.Copy() //list() // remove the GLOB.admins.Copy() and the comments if you want the pure admins_online check
|
||||
// for(var/client/C in clients_online)
|
||||
// if(C.holder) //deadmins aren't included since they wouldn't show up on adminwho
|
||||
// admins_online += C
|
||||
var/who = clients_online.Join(", ")
|
||||
var/adminwho = admins_online.Join(", ")
|
||||
|
||||
// these are typically the banning admin's, but it's the system so we leave them null, but they're still here for the sake of a full set of values
|
||||
var/sql_a_ckey
|
||||
var/sql_a_computerid
|
||||
var/sql_a_ip
|
||||
var/datum/db_query/query_add_ban = SSdbcore.NewQuery({"
|
||||
INSERT INTO [format_table_name("ban")]
|
||||
(bantime, server_ip, server_port , round_id, bantype, reason, job, duration, expiration_time, ckey, computerid, ip, a_ckey, a_computerid, a_ip, who, adminwho)
|
||||
VALUES (Now(), INET_ATON(:server_ip), :server_port, :round_id, :bantype_str, :reason, :role, :duration, Now() + INTERVAL :duration MINUTE, :ckey, :computerid, INET_ATON(:ip), :a_ckey, :a_computerid, INET_ATON(:a_ip), :who, :adminwho)"},
|
||||
list(
|
||||
// Server info
|
||||
"server_ip" = world.internet_address || 0,
|
||||
"server_port" = world.port,
|
||||
"round_id" = GLOB.round_id,
|
||||
// Client ban info
|
||||
"bantype_str" = "ADMIN_PERMABAN",
|
||||
"reason" = "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification.",
|
||||
"role" = null,
|
||||
"duration" = -1,
|
||||
"ckey" = player_ckey,
|
||||
"ip" = client.address || null,
|
||||
"computerid" = client.computer_id || null,
|
||||
// Admin banning info
|
||||
"a_ckey" = "SYSTEM (Automated-Age-Gate)", // the server
|
||||
"a_ip" = null, //key_name
|
||||
"a_computerid" = "0",
|
||||
"who" = who,
|
||||
"adminwho" = adminwho
|
||||
))
|
||||
|
||||
// record all admins and non-admins online at the time
|
||||
var/who
|
||||
for(var/client/C in GLOB.clients)
|
||||
if(!who)
|
||||
who = "[C]"
|
||||
else
|
||||
who += ", [C]"
|
||||
client.add_system_note("Automated-Age-Gate", "Failed automatic age gate process.")
|
||||
if(!query_add_ban.Execute())
|
||||
// this is the part where you should panic.
|
||||
qdel(query_add_ban)
|
||||
message_admins("WARNING! Failed to ban [ckey] for failing the automatic age gate.")
|
||||
send2tgs_adminless_only("WARNING! Failed to ban [ckey] for failing the automatic age gate.")
|
||||
qdel(client)
|
||||
return FALSE
|
||||
qdel(query_add_ban)
|
||||
|
||||
var/adminwho
|
||||
for(var/client/C in GLOB.admins)
|
||||
if(!adminwho)
|
||||
adminwho = "[C]"
|
||||
else
|
||||
adminwho += ", [C]"
|
||||
create_message("note", player_ckey, "SYSTEM (Automated-Age-Gate)", "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification.", null, null, 0, 0, null, 0, "high")
|
||||
|
||||
var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`round_id`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')"
|
||||
var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql)
|
||||
qdel(query_add_ban)
|
||||
// announce this
|
||||
message_admins("[ckey] has been banned for failing the automatic age gate.")
|
||||
send2tgs_adminless_only("[ckey] has been banned for failing the automatic age gate.")
|
||||
|
||||
// announce this
|
||||
message_admins("[html_encode(client.ckey)] has been banned for failing the automatic age gate.")
|
||||
send2irc("[html_encode(client.ckey)] has been banned for failing the automatic age gate.")
|
||||
// removing the client disconnects them
|
||||
qdel(client)
|
||||
|
||||
// removing the client disconnects them
|
||||
qdel(client)
|
||||
return FALSE
|
||||
|
||||
//they claim to be of age, so allow them to continue and update their flags
|
||||
client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE)
|
||||
client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, FALSE)
|
||||
//log this
|
||||
message_admins("[ckey] has joined through the automated age gate process.")
|
||||
|
||||
return FALSE
|
||||
else
|
||||
//they claim to be of age, so allow them to continue and update their flags
|
||||
client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE)
|
||||
client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, FALSE)
|
||||
//log this
|
||||
message_admins("[ckey] has joined through the automated age gate process.")
|
||||
return TRUE
|
||||
return TRUE
|
||||
|
||||
/mob/dead/new_player/Topic(href, href_list[])
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
|
||||
/mob/dead/new_player/proc/handle_player_polling()
|
||||
if(!SSdbcore.IsConnected())
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
|
||||
return
|
||||
var/datum/DBQuery/query_poll_get = SSdbcore.NewQuery("SELECT id, question FROM [format_table_name("poll_question")] WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")]")
|
||||
var/datum/db_query/query_poll_get = SSdbcore.NewQuery({"
|
||||
SELECT id, question
|
||||
FROM [format_table_name("poll_question")]
|
||||
WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")]
|
||||
"})
|
||||
if(!query_poll_get.warn_execute())
|
||||
qdel(query_poll_get)
|
||||
return
|
||||
@@ -27,9 +31,15 @@
|
||||
if(!pollid)
|
||||
return
|
||||
if (!SSdbcore.Connect())
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
|
||||
return
|
||||
var/datum/DBQuery/query_poll_get_details = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]")
|
||||
var/datum/db_query/query_poll_get_details = SSdbcore.NewQuery({"
|
||||
SELECT starttime, endtime, question, polltype, multiplechoiceoptions
|
||||
FROM [format_table_name("poll_question")]
|
||||
WHERE id = :id
|
||||
"}, list(
|
||||
"id" = pollid
|
||||
))
|
||||
if(!query_poll_get_details.warn_execute())
|
||||
qdel(query_poll_get_details)
|
||||
return
|
||||
@@ -47,7 +57,14 @@
|
||||
qdel(query_poll_get_details)
|
||||
switch(polltype)
|
||||
if(POLLTYPE_OPTION)
|
||||
var/datum/DBQuery/query_option_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'")
|
||||
var/datum/db_query/query_option_get_votes = SSdbcore.NewQuery({"
|
||||
SELECT optionid
|
||||
FROM [format_table_name("poll_vote")]
|
||||
WHERE pollid = :id AND ckey = :ckey
|
||||
"}, list(
|
||||
"id" = pollid,
|
||||
"ckey" = ckey
|
||||
))
|
||||
if(!query_option_get_votes.warn_execute())
|
||||
qdel(query_option_get_votes)
|
||||
return
|
||||
@@ -56,7 +73,13 @@
|
||||
votedoptionid = text2num(query_option_get_votes.item[1])
|
||||
qdel(query_option_get_votes)
|
||||
var/list/datum/polloption/options = list()
|
||||
var/datum/DBQuery/query_option_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]")
|
||||
var/datum/db_query/query_option_options = SSdbcore.NewQuery({"
|
||||
SELECT id, text
|
||||
FROM [format_table_name("poll_option")]
|
||||
WHERE pollid = :id
|
||||
"}, list(
|
||||
"id" = pollid
|
||||
))
|
||||
if(!query_option_options.warn_execute())
|
||||
qdel(query_option_options)
|
||||
return
|
||||
@@ -92,7 +115,14 @@
|
||||
src << browse(null ,"window=playerpolllist")
|
||||
src << browse(output,"window=playerpoll;size=500x250")
|
||||
if(POLLTYPE_TEXT)
|
||||
var/datum/DBQuery/query_text_get_votes = SSdbcore.NewQuery("SELECT replytext FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'")
|
||||
var/datum/db_query/query_text_get_votes = SSdbcore.NewQuery({"
|
||||
SELECT replytext
|
||||
FROM [format_table_name("poll_textreply")]
|
||||
WHERE pollid = :id AND ckey = :ckey
|
||||
"}, list(
|
||||
"id" = pollid,
|
||||
"ckey" = ckey
|
||||
))
|
||||
if(!query_text_get_votes.warn_execute())
|
||||
qdel(query_text_get_votes)
|
||||
return
|
||||
@@ -120,7 +150,13 @@
|
||||
src << browse(null ,"window=playerpolllist")
|
||||
src << browse(output,"window=playerpoll;size=500x500")
|
||||
if(POLLTYPE_RATING)
|
||||
var/datum/DBQuery/query_rating_get_votes = SSdbcore.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v WHERE o.pollid = [pollid] AND v.ckey = '[ckey]' AND o.id = v.optionid")
|
||||
var/datum/db_query/query_rating_get_votes = SSdbcore.NewQuery({"
|
||||
SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v
|
||||
WHERE o.pollid = :id AND v.ckey = :ckey AND o.id = v.optionid
|
||||
"}, list(
|
||||
"id" = pollid,
|
||||
"ckey" = ckey
|
||||
))
|
||||
if(!query_rating_get_votes.warn_execute())
|
||||
qdel(query_rating_get_votes)
|
||||
return
|
||||
@@ -140,7 +176,13 @@
|
||||
output += "<input type='hidden' name='votetype' value=[POLLTYPE_RATING]>"
|
||||
var/minid = 999999
|
||||
var/maxid = 0
|
||||
var/datum/DBQuery/query_rating_options = SSdbcore.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM [format_table_name("poll_option")] WHERE pollid = [pollid]")
|
||||
var/datum/db_query/query_rating_options = SSdbcore.NewQuery({"
|
||||
SELECT id, text, minval, maxval, descmin, descmid, descmax
|
||||
FROM [format_table_name("poll_option")]
|
||||
WHERE pollid = :id
|
||||
"}, list(
|
||||
"id" = pollid
|
||||
))
|
||||
if(!query_rating_options.warn_execute())
|
||||
qdel(query_rating_options)
|
||||
return
|
||||
@@ -177,7 +219,14 @@
|
||||
src << browse(null ,"window=playerpolllist")
|
||||
src << browse(output,"window=playerpoll;size=500x500")
|
||||
if(POLLTYPE_MULTI)
|
||||
var/datum/DBQuery/query_multi_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'")
|
||||
var/datum/db_query/query_multi_get_votes = SSdbcore.NewQuery({"
|
||||
SELECT optionid
|
||||
FROM [format_table_name("poll_vote")]
|
||||
WHERE pollid = :id AND ckey = :ckey
|
||||
"}, list(
|
||||
"id" = pollid,
|
||||
"ckey" = ckey
|
||||
))
|
||||
if(!query_multi_get_votes.warn_execute())
|
||||
qdel(query_multi_get_votes)
|
||||
return
|
||||
@@ -188,7 +237,13 @@
|
||||
var/list/datum/polloption/options = list()
|
||||
var/maxoptionid = 0
|
||||
var/minoptionid = 0
|
||||
var/datum/DBQuery/query_multi_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]")
|
||||
var/datum/db_query/query_multi_options = SSdbcore.NewQuery({"
|
||||
SELECT id, text
|
||||
FROM [format_table_name("poll_option")]
|
||||
WHERE pollid = :id
|
||||
"}, list(
|
||||
"id" = pollid
|
||||
))
|
||||
if(!query_multi_options.warn_execute())
|
||||
qdel(query_multi_options)
|
||||
return
|
||||
@@ -231,8 +286,10 @@
|
||||
if(POLLTYPE_IRV)
|
||||
var/datum/asset/irv_assets = get_asset_datum(/datum/asset/group/irv)
|
||||
irv_assets.send(src)
|
||||
|
||||
var/datum/DBQuery/query_irv_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'")
|
||||
var/datum/db_query/query_irv_get_votes = SSdbcore.NewQuery({"
|
||||
SELECT optionid FROM [format_table_name("poll_vote")]
|
||||
WHERE pollid = :pollid AND ckey = :ckey AND deleted = 0
|
||||
"}, list("pollid" = pollid, "ckey" = ckey))
|
||||
if(!query_irv_get_votes.warn_execute())
|
||||
qdel(query_irv_get_votes)
|
||||
return
|
||||
@@ -244,7 +301,13 @@
|
||||
|
||||
var/list/datum/polloption/options = list()
|
||||
|
||||
var/datum/DBQuery/query_irv_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]")
|
||||
var/datum/db_query/query_irv_options = SSdbcore.NewQuery({"
|
||||
SELECT id, text
|
||||
FROM [format_table_name("poll_option")]
|
||||
WHERE pollid = :id
|
||||
"}, list(
|
||||
"id" = pollid
|
||||
))
|
||||
if(!query_irv_options.warn_execute())
|
||||
qdel(query_irv_options)
|
||||
return
|
||||
@@ -352,16 +415,23 @@
|
||||
if (text)
|
||||
table = "poll_textreply"
|
||||
if (!SSdbcore.Connect())
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
|
||||
return
|
||||
var/datum/DBQuery/query_hasvoted = SSdbcore.NewQuery("SELECT id FROM `[format_table_name(table)]` WHERE pollid = [pollid] AND ckey = '[ckey]'")
|
||||
var/datum/db_query/query_hasvoted = SSdbcore.NewQuery({"
|
||||
SELECT id
|
||||
FROM `[format_table_name(table)]`
|
||||
WHERE pollid = :id AND ckey = :ckey
|
||||
"}, list(
|
||||
"id" = pollid,
|
||||
"ckey" = ckey
|
||||
))
|
||||
if(!query_hasvoted.warn_execute())
|
||||
qdel(query_hasvoted)
|
||||
return
|
||||
if(query_hasvoted.NextRow())
|
||||
qdel(query_hasvoted)
|
||||
if(!silent)
|
||||
to_chat(usr, "<span class='danger'>You've already replied to this poll.</span>")
|
||||
to_chat(usr, "<span class='danger'>You've already replied to this poll.</span>", confidential = TRUE)
|
||||
return TRUE
|
||||
qdel(query_hasvoted)
|
||||
return FALSE
|
||||
@@ -376,24 +446,31 @@
|
||||
/mob/dead/new_player/proc/vote_rig_check()
|
||||
if (usr != src)
|
||||
if (!usr || !src)
|
||||
return 0
|
||||
return FALSE
|
||||
//we gots ourselfs a dirty cheater on our hands!
|
||||
log_game("[key_name(usr)] attempted to rig the vote by voting as [key]")
|
||||
message_admins("[key_name_admin(usr)] attempted to rig the vote by voting as [key]")
|
||||
to_chat(usr, "<span class='danger'>You don't seem to be [key].</span>")
|
||||
to_chat(src, "<span class='danger'>Something went horribly wrong processing your vote. Please contact an administrator, they should have gotten a message about this</span>")
|
||||
return 0
|
||||
return 1
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/mob/dead/new_player/proc/vote_valid_check(pollid, holder, type)
|
||||
if (!SSdbcore.Connect())
|
||||
if(!SSdbcore.Connect())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return 0
|
||||
return
|
||||
pollid = text2num(pollid)
|
||||
if (!pollid || pollid < 0)
|
||||
return 0
|
||||
//validate the poll is actually the right type of poll and its still active
|
||||
var/datum/DBQuery/query_validate_poll = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime AND polltype = '[type]' [(holder ? "" : "AND adminonly = false")]")
|
||||
var/datum/db_query/query_validate_poll = SSdbcore.NewQuery({"
|
||||
SELECT id
|
||||
FROM [format_table_name("poll_question")]
|
||||
WHERE id = :id AND Now() BETWEEN starttime AND endtime AND polltype = :type [(holder ? "" : "AND adminonly = false")]
|
||||
"}, list(
|
||||
"id" = pollid,
|
||||
"type" = type
|
||||
))
|
||||
if(!query_validate_poll.warn_execute())
|
||||
qdel(query_validate_poll)
|
||||
return 0
|
||||
@@ -403,88 +480,66 @@
|
||||
qdel(query_validate_poll)
|
||||
return 1
|
||||
|
||||
/**
|
||||
* Processes vote form data and saves results to the database for an IRV type poll.
|
||||
*
|
||||
*/
|
||||
/mob/dead/new_player/proc/vote_on_irv_poll(pollid, list/votelist)
|
||||
if (!SSdbcore.Connect())
|
||||
if(!SSdbcore.Connect())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return 0
|
||||
if (!vote_rig_check())
|
||||
return 0
|
||||
pollid = text2num(pollid)
|
||||
if (!pollid || pollid < 0)
|
||||
return 0
|
||||
if (!votelist || !istype(votelist) || !votelist.len)
|
||||
return 0
|
||||
if (!client)
|
||||
return 0
|
||||
//save these now so we can still process the vote if the client goes away while we process.
|
||||
var/datum/admins/holder = client.holder
|
||||
var/rank = "Player"
|
||||
if (holder)
|
||||
rank = holder.rank.name
|
||||
var/ckey = client.ckey
|
||||
var/address = client.address
|
||||
|
||||
//validate the poll
|
||||
if (!vote_valid_check(pollid, holder, POLLTYPE_IRV))
|
||||
return
|
||||
if(IsAdminAdvancedProcCall())
|
||||
return
|
||||
if(!vote_rig_check())
|
||||
return
|
||||
if(!pollid)
|
||||
return
|
||||
// var/list/votelist = splittext(href_list["IRVdata"], ",")
|
||||
if(!length(votelist))
|
||||
to_chat(src, "<span class='danger'>No ordering data found. Please try again or contact an administrator.</span>")
|
||||
var/admin_rank = "Player"
|
||||
if(!QDELETED(client) && client.holder)
|
||||
admin_rank = client.holder.rank.name
|
||||
if (!vote_valid_check(pollid, client?.holder, POLLTYPE_IRV))
|
||||
return 0
|
||||
|
||||
//lets collect the options
|
||||
var/datum/DBQuery/query_irv_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE pollid = [pollid]")
|
||||
if(!query_irv_id.warn_execute())
|
||||
qdel(query_irv_id)
|
||||
return 0
|
||||
var/list/optionlist = list()
|
||||
while (query_irv_id.NextRow())
|
||||
optionlist += text2num(query_irv_id.item[1])
|
||||
qdel(query_irv_id)
|
||||
var/list/special_columns = list(
|
||||
"datetime" = "NOW()",
|
||||
"ip" = "INET_ATON(?)",
|
||||
)
|
||||
|
||||
//validate their votes are actually in the list of options and actually numbers
|
||||
var/list/numberedvotelist = list()
|
||||
for (var/vote in votelist)
|
||||
vote = text2num(vote)
|
||||
numberedvotelist += vote
|
||||
if (!vote) //this is fine because voteid starts at 1, so it will never be 0
|
||||
to_chat(src, "<span class='danger'>Error: Invalid (non-numeric) votes in the vote data.</span>")
|
||||
return 0
|
||||
if (!(vote in optionlist))
|
||||
to_chat(src, "<span class='danger'>Votes for choices that do not appear to be in the poll detected.</span>")
|
||||
return 0
|
||||
if (!numberedvotelist.len)
|
||||
to_chat(src, "<span class='danger'>Invalid vote data</span>")
|
||||
return 0
|
||||
|
||||
//lets add the vote, first we generate an insert statement.
|
||||
|
||||
var/sqlrowlist = ""
|
||||
for (var/vote in numberedvotelist)
|
||||
if (sqlrowlist != "")
|
||||
sqlrowlist += ", " //a comma (,) at the start of the first row to insert will trigger a SQL error
|
||||
sqlrowlist += "(Now(), [pollid], [vote], '[sanitizeSQL(ckey)]', INET_ATON('[sanitizeSQL(address)]'), '[sanitizeSQL(rank)]')"
|
||||
|
||||
//now lets delete their old votes (if any)
|
||||
var/datum/DBQuery/query_irv_del_old = SSdbcore.NewQuery("DELETE FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'")
|
||||
if(!query_irv_del_old.warn_execute())
|
||||
qdel(query_irv_del_old)
|
||||
return 0
|
||||
qdel(query_irv_del_old)
|
||||
|
||||
//now to add the new ones.
|
||||
var/datum/DBQuery/query_irv_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES [sqlrowlist]")
|
||||
if(!query_irv_vote.warn_execute())
|
||||
qdel(query_irv_vote)
|
||||
return 0
|
||||
qdel(query_irv_vote)
|
||||
if(!QDELETED(src))
|
||||
src << browse(null,"window=playerpoll")
|
||||
return 1
|
||||
var/sql_votes = list()
|
||||
for(var/o in votelist)
|
||||
var/voteid = text2num(o)
|
||||
if(!voteid)
|
||||
continue
|
||||
sql_votes += list(list(
|
||||
"pollid" = pollid,
|
||||
"optionid" = voteid,
|
||||
"ckey" = ckey,
|
||||
"ip" = client.address,
|
||||
"adminrank" = admin_rank
|
||||
))
|
||||
//IRV results are calculated based on id order, we delete all of a user's votes to avoid potential errors caused by revoting and option editing
|
||||
var/datum/db_query/query_delete_irv_votes = SSdbcore.NewQuery({"
|
||||
UPDATE [format_table_name("poll_vote")] SET deleted = 1 WHERE pollid = :pollid AND ckey = :ckey
|
||||
"}, list("pollid" = pollid, "ckey" = ckey))
|
||||
if(!query_delete_irv_votes.warn_execute())
|
||||
qdel(query_delete_irv_votes)
|
||||
return
|
||||
qdel(query_delete_irv_votes)
|
||||
SSdbcore.MassInsert(format_table_name("poll_vote"), sql_votes, special_columns = special_columns)
|
||||
return TRUE
|
||||
|
||||
|
||||
/mob/dead/new_player/proc/vote_on_poll(pollid, optionid)
|
||||
if (!SSdbcore.Connect())
|
||||
if(!SSdbcore.Connect())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return 0
|
||||
if (!vote_rig_check())
|
||||
return 0
|
||||
return
|
||||
if(!vote_rig_check())
|
||||
return
|
||||
if(IsAdminAdvancedProcCall())
|
||||
return
|
||||
if(!pollid || !optionid)
|
||||
return
|
||||
//validate the poll
|
||||
@@ -493,10 +548,19 @@
|
||||
var/voted = poll_check_voted(pollid)
|
||||
if(isnull(voted) || voted) //Failed or already voted.
|
||||
return
|
||||
var/adminrank = sanitizeSQL(poll_rank())
|
||||
var/adminrank = poll_rank()
|
||||
if(!adminrank)
|
||||
return
|
||||
var/datum/DBQuery/query_option_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')")
|
||||
var/datum/db_query/query_option_vote = SSdbcore.NewQuery({"
|
||||
INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank)
|
||||
VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank)
|
||||
"}, list(
|
||||
"pollid" = pollid,
|
||||
"optionid" = optionid,
|
||||
"ckey" = ckey,
|
||||
"address" = client.address,
|
||||
"adminrank" = adminrank
|
||||
))
|
||||
if(!query_option_vote.warn_execute())
|
||||
qdel(query_option_vote)
|
||||
return
|
||||
@@ -506,11 +570,13 @@
|
||||
return 1
|
||||
|
||||
/mob/dead/new_player/proc/log_text_poll_reply(pollid, replytext)
|
||||
if (!SSdbcore.Connect())
|
||||
if(!SSdbcore.Connect())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return 0
|
||||
if (!vote_rig_check())
|
||||
return 0
|
||||
return
|
||||
if(!vote_rig_check())
|
||||
return
|
||||
if(IsAdminAdvancedProcCall())
|
||||
return
|
||||
if(!pollid)
|
||||
return
|
||||
//validate the poll
|
||||
@@ -522,18 +588,23 @@
|
||||
var/voted = poll_check_voted(pollid, text = TRUE, silent = TRUE)
|
||||
if(isnull(voted))
|
||||
return
|
||||
var/adminrank = sanitizeSQL(poll_rank())
|
||||
var/adminrank = poll_rank()
|
||||
if(!adminrank)
|
||||
return
|
||||
replytext = sanitizeSQL(replytext)
|
||||
if(!(length(replytext) > 0) || !(length(replytext) <= 8000))
|
||||
to_chat(usr, "The text you entered was invalid or too long. Please correct the text and submit again.")
|
||||
return
|
||||
var/datum/DBQuery/query_text_vote
|
||||
var/datum/db_query/query_text_vote
|
||||
if(!voted)
|
||||
query_text_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_textreply")] (datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (Now(), [pollid], '[ckey]', INET_ATON('[client.address]'), '[replytext]', '[adminrank]')")
|
||||
query_text_vote = SSdbcore.NewQuery({"
|
||||
INSERT INTO [format_table_name("poll_textreply")] (datetime, pollid, ckey, ip, replytext, adminrank)
|
||||
VALUES (Now(), :pollid, :ckey, INET_ATON(:address), :replytext, :adminrank)
|
||||
"}, list("pollid" = pollid, "ckey" = ckey, "address" = client.address, "replytext" = replytext, "adminrank" = adminrank))
|
||||
else
|
||||
query_text_vote = SSdbcore.NewQuery("UPDATE [format_table_name("poll_textreply")] SET datetime = Now(), ip = INET_ATON('[client.address]'), replytext = '[replytext]' WHERE pollid = '[pollid]' AND ckey = '[ckey]'")
|
||||
query_text_vote = SSdbcore.NewQuery({"
|
||||
UPDATE [format_table_name("poll_textreply")]
|
||||
SET datetime = Now(), ip = INET_ATON(:address), replytext = :replytext WHERE pollid = :pollid AND ckey = :ckey
|
||||
"}, list("address" = client.address, "replytext" = replytext, "pollid" = pollid, "ckey" = ckey))
|
||||
if(!query_text_vote.warn_execute())
|
||||
qdel(query_text_vote)
|
||||
return
|
||||
@@ -543,17 +614,26 @@
|
||||
return 1
|
||||
|
||||
/mob/dead/new_player/proc/vote_on_numval_poll(pollid, optionid, rating)
|
||||
if (!SSdbcore.Connect())
|
||||
if(!SSdbcore.Connect())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return 0
|
||||
if (!vote_rig_check())
|
||||
return 0
|
||||
return
|
||||
if(!vote_rig_check())
|
||||
return
|
||||
if(IsAdminAdvancedProcCall())
|
||||
return
|
||||
if(!pollid || !optionid || !rating)
|
||||
return
|
||||
//validate the poll
|
||||
if (!vote_valid_check(pollid, client.holder, POLLTYPE_RATING))
|
||||
return 0
|
||||
var/datum/DBQuery/query_numval_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND ckey = '[ckey]'")
|
||||
var/datum/db_query/query_numval_hasvoted = SSdbcore.NewQuery({"
|
||||
SELECT id
|
||||
FROM [format_table_name("poll_vote")]
|
||||
WHERE optionid = :id AND ckey = :ckey
|
||||
"}, list(
|
||||
"id" = optionid,
|
||||
"ckey" = ckey
|
||||
))
|
||||
if(!query_numval_hasvoted.warn_execute())
|
||||
qdel(query_numval_hasvoted)
|
||||
return
|
||||
@@ -565,8 +645,10 @@
|
||||
var/adminrank = "Player"
|
||||
if(client.holder)
|
||||
adminrank = client.holder.rank.name
|
||||
adminrank = sanitizeSQL(adminrank)
|
||||
var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]', [(isnull(rating)) ? "null" : rating])")
|
||||
var/datum/db_query/query_numval_vote = SSdbcore.NewQuery({"
|
||||
INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating)
|
||||
VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank, :rating)
|
||||
"}, list("pollid" = pollid, "optionid" = optionid, "ckey" = ckey, "address" = client.address, "adminrank" = adminrank, "rating" = isnull(rating) ? "null" : rating))
|
||||
if(!query_numval_vote.warn_execute())
|
||||
qdel(query_numval_vote)
|
||||
return
|
||||
@@ -575,46 +657,59 @@
|
||||
usr << browse(null,"window=playerpoll")
|
||||
return 1
|
||||
|
||||
/**
|
||||
* Processes vote form data and saves results to the database for a multiple choice type poll.
|
||||
*
|
||||
*/
|
||||
/mob/dead/new_player/proc/vote_on_multi_poll(pollid, optionid)
|
||||
if (!SSdbcore.Connect())
|
||||
if(!SSdbcore.Connect())
|
||||
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
|
||||
return 0
|
||||
if (!vote_rig_check())
|
||||
return 0
|
||||
if(!pollid || !optionid)
|
||||
return 1
|
||||
return
|
||||
if(!vote_rig_check())
|
||||
return
|
||||
if(IsAdminAdvancedProcCall())
|
||||
return
|
||||
//validate the poll
|
||||
if (!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI))
|
||||
return 0
|
||||
var/datum/DBQuery/query_multi_choicelen = SSdbcore.NewQuery("SELECT multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]")
|
||||
if(!query_multi_choicelen.warn_execute())
|
||||
qdel(query_multi_choicelen)
|
||||
return 1
|
||||
var/i
|
||||
if(query_multi_choicelen.NextRow())
|
||||
i = text2num(query_multi_choicelen.item[1])
|
||||
qdel(query_multi_choicelen)
|
||||
var/datum/DBQuery/query_multi_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'")
|
||||
if(!query_multi_hasvoted.warn_execute())
|
||||
qdel(query_multi_hasvoted)
|
||||
return 1
|
||||
while(i)
|
||||
if(query_multi_hasvoted.NextRow())
|
||||
i--
|
||||
else
|
||||
break
|
||||
qdel(query_multi_hasvoted)
|
||||
if(!i)
|
||||
return 2
|
||||
var/adminrank = "Player"
|
||||
if(!QDELETED(client) && client.holder)
|
||||
adminrank = client.holder.rank.name
|
||||
adminrank = sanitizeSQL(adminrank)
|
||||
var/datum/DBQuery/query_multi_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')")
|
||||
if(!query_multi_vote.warn_execute())
|
||||
qdel(query_multi_vote)
|
||||
return 1
|
||||
qdel(query_multi_vote)
|
||||
if(!QDELETED(usr))
|
||||
usr << browse(null,"window=playerpoll")
|
||||
return 0
|
||||
if(!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI))
|
||||
return
|
||||
if(!pollid || !optionid)
|
||||
return
|
||||
// if(length(href_list) > 2)
|
||||
// href_list.Cut(1,3) //first two values aren't options
|
||||
// else
|
||||
// to_chat(src, "<span class='danger'>No options were selected.</span>")
|
||||
|
||||
var/special_columns = list(
|
||||
"datetime" = "NOW()",
|
||||
"ip" = "INET_ATON(?)",
|
||||
)
|
||||
|
||||
var/sql_votes = list()
|
||||
// var/vote_count = 0
|
||||
// for(var/h in href_list)
|
||||
// if(vote_count == poll.options_allowed)
|
||||
// to_chat(src, "<span class='danger'>Allowed option count exceeded, only the first [poll.options_allowed] selected options have been saved.</span>")
|
||||
// break
|
||||
// vote_count++
|
||||
// var/datum/poll_option/option = locate(h) in poll.options
|
||||
var/admin_rank = "Player"
|
||||
if(!QDELETED(client) && client?.holder)
|
||||
admin_rank = client.holder.rank.name
|
||||
sql_votes += list(list(
|
||||
"pollid" = pollid,
|
||||
"optionid" = optionid,
|
||||
"ckey" = ckey,
|
||||
"ip" = client.address,
|
||||
"adminrank" = admin_rank
|
||||
))
|
||||
/*with revoting and poll editing possible there can be an edge case where a poll is changed to allow less multiple choice options than a user has already voted on
|
||||
rather than trying to calculate which options should be updated and which deleted, we just delete all of a user's votes and re-insert as needed*/
|
||||
var/datum/db_query/query_delete_multi_votes = SSdbcore.NewQuery({"
|
||||
UPDATE [format_table_name("poll_vote")] SET deleted = 1 WHERE pollid = :pollid AND ckey = :ckey
|
||||
"}, list("pollid" = pollid, "ckey" = ckey))
|
||||
if(!query_delete_multi_votes.warn_execute())
|
||||
qdel(query_delete_multi_votes)
|
||||
return
|
||||
qdel(query_delete_multi_votes)
|
||||
SSdbcore.MassInsert(format_table_name("poll_vote"), sql_votes, special_columns = special_columns)
|
||||
return TRUE
|
||||
|
||||
@@ -298,20 +298,21 @@
|
||||
return doUnEquip(I, force, drop_location(), FALSE)
|
||||
|
||||
//for when the item will be immediately placed in a loc other than the ground
|
||||
/mob/proc/transferItemToLoc(obj/item/I, newloc = null, force = FALSE)
|
||||
return doUnEquip(I, force, newloc, FALSE)
|
||||
/mob/proc/transferItemToLoc(obj/item/I, newloc = null, force = FALSE, silent = TRUE)
|
||||
return doUnEquip(I, force, newloc, FALSE, silent = silent)
|
||||
|
||||
//visibly unequips I but it is NOT MOVED AND REMAINS IN SRC
|
||||
//item MUST BE FORCEMOVE'D OR QDEL'D
|
||||
/mob/proc/temporarilyRemoveItemFromInventory(obj/item/I, force = FALSE, idrop = TRUE)
|
||||
return doUnEquip(I, force, null, TRUE, idrop)
|
||||
return doUnEquip(I, force, null, TRUE, idrop, silent = TRUE)
|
||||
|
||||
//DO NOT CALL THIS PROC
|
||||
//use one of the above 3 helper procs
|
||||
//you may override it, but do not modify the args
|
||||
/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress.
|
||||
/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress.
|
||||
//Use no_move if the item is just gonna be immediately moved afterward
|
||||
//Invdrop is used to prevent stuff in pockets dropping. only set to false if it's going to immediately be replaced
|
||||
PROTECTED_PROC(TRUE)
|
||||
if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for TRAIT_NODROP.
|
||||
return TRUE
|
||||
|
||||
@@ -333,9 +334,7 @@
|
||||
I.moveToNullspace()
|
||||
else
|
||||
I.forceMove(newloc)
|
||||
on_item_dropped(I)
|
||||
if(I.dropped(src) == ITEM_RELOCATED_BY_DROPPED)
|
||||
return FALSE
|
||||
I.dropped(src, silent)
|
||||
return TRUE
|
||||
|
||||
//This is a SAFE proc. Use this instead of equip_to_slot()!
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
//can't unequip since it can't equip anything
|
||||
/mob/living/carbon/alien/larva/doUnEquip(obj/item/W)
|
||||
/mob/living/carbon/alien/larva/doUnEquip(obj/item/W, silent = FALSE)
|
||||
return
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
var/lastpuke = 0
|
||||
var/account_id
|
||||
var/last_fire_update
|
||||
var/hardcore_survival_score = 0
|
||||
|
||||
/// Unarmed parry data for human
|
||||
/datum/block_parry_data/unarmed/human
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
var/obj/item/thing = sloties
|
||||
. += thing?.slowdown
|
||||
|
||||
/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE)
|
||||
/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE)
|
||||
var/index = get_held_index_of_item(I)
|
||||
. = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should.
|
||||
if(!. || !I)
|
||||
|
||||
@@ -3,16 +3,30 @@
|
||||
GLOBAL_LIST_EMPTY(roundstart_races)
|
||||
GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
/**
|
||||
* # species datum
|
||||
*
|
||||
* Datum that handles different species in the game.
|
||||
*
|
||||
* This datum handles species in the game, such as lizardpeople, mothmen, zombies, skeletons, etc.
|
||||
* It is used in [carbon humans][mob/living/carbon/human] to determine various things about them, like their food preferences, if they have biological genders, their damage resistances, and more.
|
||||
*
|
||||
*/
|
||||
/datum/species
|
||||
var/id // if the game needs to manually check your race to do something not included in a proc here, it will use this
|
||||
var/limbs_id //this is used if you want to use a different species limb sprites. Mainly used for angels as they look like humans.
|
||||
var/name // this is the fluff name. these will be left generic (such as 'Lizardperson' for the lizard race) so servers can change them to whatever
|
||||
var/default_color = "#FFFFFF" // if alien colors are disabled, this is the color that will be used by that race
|
||||
///If the game needs to manually check your race to do something not included in a proc here, it will use this.
|
||||
var/id
|
||||
//This is used if you want to use a different species' limb sprites.
|
||||
var/limbs_id
|
||||
///This is the fluff name. They are displayed on health analyzers and in the character setup menu. Leave them generic for other servers to customize.
|
||||
var/name
|
||||
// Default color. If mutant colors are disabled, this is the color that will be used by that race.
|
||||
var/default_color = "#FFF"
|
||||
|
||||
var/sexes = 1 // whether or not the race has sexual characteristics. at the moment this is only 0 for skeletons and shadows
|
||||
///Whether or not the race has sexual characteristics (biological genders). At the moment this is only FALSE for skeletons and shadows
|
||||
var/sexes = TRUE
|
||||
var/has_field_of_vision = TRUE
|
||||
|
||||
//Species Icon Drawing Offsets - Pixel X, Pixel Y, Aka X = Horizontal and Y = Vertical, from bottom left corner
|
||||
///Clothing offsets. If a species has a different body than other species, you can offset clothing so they look less weird.
|
||||
var/list/offset_features = list(
|
||||
OFFSET_UNIFORM = list(0,0),
|
||||
OFFSET_ID = list(0,0),
|
||||
@@ -34,71 +48,141 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
OFFSET_MUTPARTS = list(0,0)
|
||||
)
|
||||
|
||||
var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color
|
||||
var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent.
|
||||
var/use_skintones = NO_SKINTONES // does it use skintones or not? (spoiler alert this is only used by humans)
|
||||
var/exotic_blood = "" // If your race wants to bleed something other than bog standard blood, change this to reagent id.
|
||||
var/exotic_bloodtype = "" //If your race uses a non standard bloodtype (A+, O-, AB-, etc)
|
||||
var/exotic_blood_color = BLOOD_COLOR_HUMAN //assume human as the default blood colour, override this default by species subtypes
|
||||
///This allows races to have specific hair colors. If null, it uses the H's hair/facial hair colors. If "mutcolor", it uses the H's mutant_color. If "fixedmutcolor", it uses fixedmutcolor
|
||||
var/hair_color
|
||||
///The alpha used by the hair. 255 is completely solid, 0 is invisible.
|
||||
var/hair_alpha = 255
|
||||
|
||||
///Does the species use skintones or not? As of now only used by humans.
|
||||
var/use_skintones = FALSE
|
||||
///If your race bleeds something other than bog standard blood, change this to reagent id. For example, ethereals bleed liquid electricity.
|
||||
var/exotic_blood = ""
|
||||
///If your race uses a non standard bloodtype (A+, O-, AB-, etc). For example, lizards have L type blood.
|
||||
var/exotic_bloodtype = ""
|
||||
/// Assume human as the default blood colour, override this default by species subtypes
|
||||
var/exotic_blood_color = BLOOD_COLOR_HUMAN
|
||||
///What the species drops when gibbed by a gibber machine.
|
||||
var/meat = /obj/item/reagent_containers/food/snacks/meat/slab/human //What the species drops on gibbing
|
||||
var/list/gib_types = list(/obj/effect/gibspawner/human, /obj/effect/gibspawner/human/bodypartless)
|
||||
///What skin the species drops when gibbed by a gibber machine.
|
||||
var/skinned_type
|
||||
///Bitfield for food types that the species likes, giving them a mood boost. Lizards like meat, for example.
|
||||
var/liked_food = NONE
|
||||
///Bitfield for food types that the species dislikes, giving them disgust. Humans hate raw food, for example.
|
||||
var/disliked_food = GROSS
|
||||
///Bitfield for food types that the species absolutely hates, giving them even more disgust than disliked food. Meat is "toxic" to moths, for example.
|
||||
var/toxic_food = TOXIC
|
||||
var/list/no_equip = list() // 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
|
||||
///Inventory slots the race can't equip stuff to. Golems cannot wear jumpsuits, for example.
|
||||
var/list/no_equip = list()
|
||||
/// Allows the species to equip items that normally require a jumpsuit without having one equipped. Used by golems.
|
||||
var/nojumpsuit = FALSE
|
||||
var/blacklisted = 0 //Flag to exclude from green slime core species.
|
||||
var/dangerous_existence //A flag for transformation spells that tells them "hey if you turn a person into one of these without preperation, they'll probably die!"
|
||||
var/say_mod = "says" // affects the speech message
|
||||
///Affects the speech message, for example: Motharula flutters, "My speech message is flutters!"
|
||||
var/say_mod = "says"
|
||||
///What languages this species can understand and say. Use a [language holder datum][/datum/language_holder] in this var.
|
||||
var/species_language_holder = /datum/language_holder
|
||||
var/list/mutant_bodyparts = list() // Visible CURRENT bodyparts that are unique to a species. Changes to this list for non-species specific bodyparts (ie cat ears and tails) should be assigned at organ level if possible. Layer hiding is handled by handle_mutant_bodyparts() below.
|
||||
var/list/mutant_organs = list() //Internal organs that are unique to this race.
|
||||
var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster
|
||||
var/armor = 0 // overall defense for the race... or less defense, if it's negative.
|
||||
var/attack_type = BRUTE // the type of damage unarmed attacks from this species do
|
||||
var/brutemod = 1 // multiplier for brute damage
|
||||
var/burnmod = 1 // multiplier for burn damage
|
||||
var/coldmod = 1 // multiplier for cold damage
|
||||
var/heatmod = 1 // multiplier for heat damage
|
||||
var/stunmod = 1 // multiplier for stun duration
|
||||
var/punchdamagelow = 1 //lowest possible punch damage. if this is set to 0, punches will always miss
|
||||
var/punchdamagehigh = 10 //highest possible punch damage
|
||||
var/punchstunthreshold = 10 //damage at which punches from this race will stun //yes it should be to the attacked race but it's not useful that way even if it's logical
|
||||
/**
|
||||
* Visible CURRENT bodyparts that are unique to a species.
|
||||
* DO NOT USE THIS AS A LIST OF ALL POSSIBLE BODYPARTS AS IT WILL FUCK
|
||||
* SHIT UP! Changes to this list for non-species specific bodyparts (ie
|
||||
* cat ears and tails) should be assigned at organ level if possible.
|
||||
* Assoc values are defaults for given bodyparts, also modified by aforementioned organs.
|
||||
* They also allow for faster '[]' list access versus 'in'. Other than that, they are useless right now.
|
||||
* Layer hiding is handled by [/datum/species/proc/handle_mutant_bodyparts] below.
|
||||
*/
|
||||
var/list/mutant_bodyparts = list()
|
||||
///Internal organs that are unique to this race, like a tail.
|
||||
var/list/mutant_organs = list()
|
||||
///Multiplier for the race's speed. Positive numbers make it move slower, negative numbers make it move faster.
|
||||
var/speedmod = 0
|
||||
///Percentage modifier for overall defense of the race, or less defense, if it's negative.
|
||||
var/armor = 0
|
||||
///multiplier for brute damage
|
||||
var/brutemod = 1
|
||||
///multiplier for burn damage
|
||||
var/burnmod = 1
|
||||
///multiplier for damage from cold temperature
|
||||
var/coldmod = 1
|
||||
///multiplier for damage from hot temperature
|
||||
var/heatmod = 1
|
||||
///multiplier for stun durations
|
||||
var/stunmod = 1
|
||||
///multiplier for money paid at payday
|
||||
var/payday_modifier = 1
|
||||
///Type of damage attack does. Ethereals attack with burn damage for example.
|
||||
var/attack_type = BRUTE // multiplier for stun duration
|
||||
///Lowest possible punch damage this species can give. If this is set to 0, punches will always miss.
|
||||
var/punchdamagelow = 1
|
||||
///Highest possible punch damage this species can give.
|
||||
var/punchdamagehigh = 10
|
||||
///Damage at which punches from this race will stun
|
||||
var/punchstunthreshold = 10 //yes it should be to the attacked race but it's not useful that way even if it's logical
|
||||
var/punchwoundbonus = 0 // additional wound bonus. generally zero.
|
||||
var/siemens_coeff = 1 //base electrocution coefficient
|
||||
var/damage_overlay_type = "human" //what kind of damage overlays (if any) appear on our species when wounded?
|
||||
var/fixed_mut_color = "" //to use MUTCOLOR with a fixed color that's independent of dna.feature["mcolor"]
|
||||
///Base electrocution coefficient. Basically a multiplier for damage from electrocutions.
|
||||
var/siemens_coeff = 1
|
||||
///What kind of damage overlays (if any) appear on our species when wounded? If this is "", does not add an overlay.
|
||||
var/damage_overlay_type = "human"
|
||||
///To use MUTCOLOR with a fixed color that's independent of the mcolor feature in DNA.
|
||||
var/fixed_mut_color = ""
|
||||
///Special mutation that can be found in the genepool exclusively in this species. Dont leave empty or changing species will be a headache
|
||||
var/inert_mutation = DWARFISM
|
||||
var/list/special_step_sounds //Sounds to override barefeet walkng
|
||||
var/grab_sound //Special sound for grabbing
|
||||
var/datum/outfit/outfit_important_for_life // A path to an outfit that is important for species life e.g. plasmaman outfit
|
||||
///Sounds to override barefeet walking
|
||||
var/list/special_step_sounds
|
||||
///Special sound for grabbing
|
||||
var/grab_sound
|
||||
/// A path to an outfit that is important for species life e.g. plasmaman outfit
|
||||
var/datum/outfit/outfit_important_for_life
|
||||
|
||||
// species-only traits. Can be found in DNA.dm
|
||||
///Species-only traits. Can be found in [code/__DEFINES/DNA.dm]
|
||||
var/list/species_traits = list(HAS_FLESH,HAS_BONE) //by default they can scar and have bones/flesh unless set to something else
|
||||
// generic traits tied to having the species
|
||||
var/list/inherent_traits = list()
|
||||
///Generic traits tied to having the species.
|
||||
var/list/inherent_traits = list() //list(TRAIT_ADVANCEDTOOLUSER)
|
||||
/// List of biotypes the mob belongs to. Used by diseases.
|
||||
var/inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID
|
||||
|
||||
var/attack_verb = "punch" // punch-specific attack verb
|
||||
var/list/blacklisted_quirks = list() // Quirks that will be removed upon gaining this species, to be defined by species
|
||||
var/list/removed_quirks = list() // Quirks that got removed due to being blacklisted, and will be restored when on_species_loss() is called
|
||||
|
||||
///Punch-specific attack verb.
|
||||
var/attack_verb = "punch"
|
||||
///
|
||||
var/sound/attack_sound = 'sound/weapons/punch1.ogg'
|
||||
var/sound/miss_sound = 'sound/weapons/punchmiss.ogg'
|
||||
|
||||
var/list/mob/living/ignored_by = list() // list of mobs that will ignore this species
|
||||
//Breathing!
|
||||
var/obj/item/organ/lungs/mutantlungs = null
|
||||
///What gas does this species breathe? Used by suffocation screen alerts, most of actual gas breathing is handled by mutantlungs. See [life.dm][code/modules/mob/living/carbon/human/life.dm]
|
||||
var/breathid = "o2"
|
||||
|
||||
//Do NOT remove by setting to null. use OR make a RESPECTIVE TRAIT (removing stomach? add the NOSTOMACH trait to your species)
|
||||
//why does it work this way? because traits also disable the downsides of not having an organ, removing organs but not having the trait will make your species die
|
||||
|
||||
///Replaces default brain with a different organ
|
||||
var/obj/item/organ/brain/mutant_brain = /obj/item/organ/brain
|
||||
///Replaces default heart with a different organ
|
||||
var/obj/item/organ/heart/mutant_heart = /obj/item/organ/heart
|
||||
///Replaces default lungs with a different organ
|
||||
// var/obj/item/organ/lungs/mutantlungs = /obj/item/organ/lungs
|
||||
///Replaces default eyes with a different organ
|
||||
var/obj/item/organ/eyes/mutanteyes = /obj/item/organ/eyes
|
||||
///Replaces default ears with a different organ
|
||||
var/obj/item/organ/ears/mutantears = /obj/item/organ/ears
|
||||
var/obj/item/mutanthands
|
||||
///Replaces default tongue with a different organ
|
||||
var/obj/item/organ/tongue/mutanttongue = /obj/item/organ/tongue
|
||||
///Replaces default liver with a different organ
|
||||
var/obj/item/organ/liver/mutantliver = /obj/item/organ/liver
|
||||
///Replaces default stomach with a different organ
|
||||
var/obj/item/organ/stomach/mutantstomach = /obj/item/organ/stomach
|
||||
///Replaces default appendix with a different organ.
|
||||
var/obj/item/organ/appendix/mutantappendix = /obj/item/organ/appendix
|
||||
///Forces an item into this species' hands. Only an honorary mutantthing because this is not an organ and not loaded in the same way, you've been warned to do your research.
|
||||
var/obj/item/mutanthands
|
||||
|
||||
/// CIT SPECIFIC Mutant tail
|
||||
var/obj/item/organ/tail/mutanttail = null
|
||||
|
||||
var/obj/item/organ/liver/mutantliver
|
||||
var/obj/item/organ/stomach/mutantstomach
|
||||
var/override_float = FALSE
|
||||
|
||||
//Citadel snowflake
|
||||
@@ -123,6 +207,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
//the icon state of the eyes this species has
|
||||
var/eye_type = "normal"
|
||||
|
||||
///For custom overrides for species ass images
|
||||
var/icon/ass_image
|
||||
|
||||
///////////
|
||||
// PROCS //
|
||||
///////////
|
||||
@@ -138,6 +225,12 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
//update our mutant bodyparts to include unlocked ones
|
||||
mutant_bodyparts += GLOB.unlocked_mutant_parts
|
||||
|
||||
/**
|
||||
* Generates species available to choose in character setup at roundstart
|
||||
*
|
||||
* This proc generates which species are available to pick from in character setup.
|
||||
* If there are no available roundstart species, defaults to human.
|
||||
*/
|
||||
/proc/generate_selectable_species(clear = FALSE)
|
||||
if(clear)
|
||||
GLOB.roundstart_races = list()
|
||||
@@ -151,11 +244,26 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(!GLOB.roundstart_races.len)
|
||||
GLOB.roundstart_races += "human"
|
||||
|
||||
/**
|
||||
* Checks if a species is eligible to be picked at roundstart.
|
||||
*
|
||||
* Checks the config to see if this species is allowed to be picked in the character setup menu.
|
||||
* Used by [/proc/generate_selectable_species].
|
||||
*/
|
||||
/datum/species/proc/check_roundstart_eligible()
|
||||
if(id in (CONFIG_GET(keyed_list/roundstart_races)))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Generates a random name for a carbon.
|
||||
*
|
||||
* This generates a random unique name based on a human's species and gender.
|
||||
* Arguments:
|
||||
* * gender - The gender that the name should adhere to. Use MALE for male names, use anything else for female names.
|
||||
* * unique - If true, ensures that this new name is not a duplicate of anyone else's name currently on the station.
|
||||
* * lastname - Does this species' naming system adhere to the last name system? Set to false if it doesn't.
|
||||
*/
|
||||
/datum/species/proc/random_name(gender,unique,lastname)
|
||||
if(unique)
|
||||
return random_unique_name(gender)
|
||||
@@ -173,7 +281,13 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
return randname
|
||||
|
||||
//Called when cloning, copies some vars that should be kept
|
||||
/**
|
||||
* Copies some vars and properties over that should be kept when creating a copy of this species.
|
||||
*
|
||||
* Used by slimepeople to copy themselves, and by the DNA datum to hardset DNA to a species
|
||||
* Arguments:
|
||||
* * old_species - The species that the carbon used to be before copying
|
||||
*/
|
||||
/datum/species/proc/copy_properties_from(datum/species/old_species)
|
||||
mutant_bodyparts["limbs_id"] = old_species.mutant_bodyparts["limbs_id"]
|
||||
eye_type = old_species.eye_type
|
||||
@@ -186,7 +300,18 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
// return 0 //It returns false when it runs the proc so they don't get jobs from the global list.
|
||||
return 1 //It returns 1 to say they are a-okay to continue.
|
||||
|
||||
//Will regenerate missing organs
|
||||
/**
|
||||
* Corrects organs in a carbon, removing ones it doesn't need and adding ones it does.
|
||||
*
|
||||
* Takes all organ slots, removes organs a species should not have, adds organs a species should have.
|
||||
* can use replace_current to refresh all organs, creating an entirely new set.
|
||||
*
|
||||
* Arguments:
|
||||
* * C - carbon, the owner of the species datum AKA whoever we're regenerating organs in
|
||||
* * old_species - datum, used when regenerate organs is called in a switching species to remove old mutant organs.
|
||||
* * replace_current - boolean, forces all old organs to get deleted whether or not they pass the species' ability to keep that organ
|
||||
* * excluded_zones - list, add zone defines to block organs inside of the zones from getting handled. see headless mutation for an example
|
||||
*/
|
||||
/datum/species/proc/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE)
|
||||
var/obj/item/organ/brain/brain = C.getorganslot(ORGAN_SLOT_BRAIN)
|
||||
var/obj/item/organ/heart/heart = C.getorganslot(ORGAN_SLOT_HEART)
|
||||
@@ -302,6 +427,16 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
var/obj/item/organ/I = new path()
|
||||
I.Insert(C)
|
||||
|
||||
/**
|
||||
* Proc called when a carbon becomes this species.
|
||||
*
|
||||
* This sets up and adds/changes/removes things, qualities, abilities, and traits so that the transformation is as smooth and bugfree as possible.
|
||||
* Produces a [COMSIG_SPECIES_GAIN] signal.
|
||||
* Arguments:
|
||||
* * C - Carbon, this is whoever became the new species.
|
||||
* * old_species - The species that the carbon used to be before becoming this race, used for regenerating organs.
|
||||
* * pref_load - Preferences to be loaded from character setup, loads in preferred mutant things like bodyparts, digilegs, skin color, etc.
|
||||
*/
|
||||
/datum/species/proc/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
|
||||
// Drop the items the new species can't wear
|
||||
for(var/slot_id in no_equip)
|
||||
@@ -342,6 +477,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
for(var/X in inherent_traits)
|
||||
ADD_TRAIT(C, X, SPECIES_TRAIT)
|
||||
|
||||
//lets remove those conflicting quirks
|
||||
remove_blacklisted_quirks(C)
|
||||
|
||||
if(TRAIT_VIRUSIMMUNE in inherent_traits)
|
||||
for(var/datum/disease/A in C.diseases)
|
||||
A.cure(FALSE)
|
||||
@@ -395,6 +533,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
for(var/X in inherent_traits)
|
||||
REMOVE_TRAIT(C, X, SPECIES_TRAIT)
|
||||
|
||||
// lets restore the quirks that got removed when gaining this species
|
||||
restore_quirks(C)
|
||||
|
||||
C.remove_movespeed_modifier(/datum/movespeed_modifier/species)
|
||||
|
||||
if(mutant_bodyparts["meat_type"])
|
||||
@@ -424,6 +565,26 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src)
|
||||
|
||||
// shamelessly inspired by antag_datum.remove_blacklisted_quirks()
|
||||
/datum/species/proc/remove_blacklisted_quirks(mob/living/carbon/C)
|
||||
var/mob/living/L = C.mind?.current
|
||||
if(istype(L))
|
||||
var/list/my_quirks = L.client?.prefs.all_quirks.Copy()
|
||||
SSquirks.filter_quirks(my_quirks, blacklisted_quirks)
|
||||
for(var/q in L.roundstart_quirks)
|
||||
var/datum/quirk/Q = q
|
||||
if(!(SSquirks.quirk_name_by_path(Q.type) in my_quirks))
|
||||
L.remove_quirk(Q.type)
|
||||
removed_quirks += Q.type
|
||||
|
||||
// restore any quirks that we removed
|
||||
/datum/species/proc/restore_quirks(mob/living/carbon/C)
|
||||
var/mob/living/L = C.mind?.current
|
||||
if(istype(L))
|
||||
for(var/q in removed_quirks)
|
||||
L.add_quirk(q)
|
||||
|
||||
|
||||
/datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour)
|
||||
H.remove_overlay(HAIR_LAYER)
|
||||
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
|
||||
mutanttongue = /obj/item/organ/tongue/abductor
|
||||
species_category = SPECIES_CATEGORY_ALIEN
|
||||
ass_image = 'icons/ass/assgrey.png'
|
||||
|
||||
/datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species)
|
||||
. = ..()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
tail_type = "mam_tail"
|
||||
wagging_type = "mam_waggingtail"
|
||||
species_category = SPECIES_CATEGORY_FURRY
|
||||
ass_image = 'icons/ass/asscat.png'
|
||||
|
||||
/datum/species/human/felinid/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
|
||||
if(ishuman(C))
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
tail_type = "mam_tail"
|
||||
wagging_type = "mam_waggingtail"
|
||||
species_category = SPECIES_CATEGORY_JELLY
|
||||
ass_image = 'icons/ass/assslime.png'
|
||||
|
||||
/obj/item/organ/brain/jelly
|
||||
name = "slime nucleus"
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
wagging_type = "waggingtail_lizard"
|
||||
species_category = SPECIES_CATEGORY_LIZARD
|
||||
|
||||
ass_image = 'icons/ass/asslizard.png'
|
||||
|
||||
/datum/species/lizard/random_name(gender,unique,lastname)
|
||||
if(unique)
|
||||
return random_unique_lizard_name(gender)
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
species_category = SPECIES_CATEGORY_SKELETON
|
||||
|
||||
ass_image = 'icons/ass/assplasma.png'
|
||||
|
||||
/datum/species/plasmaman/spec_life(mob/living/carbon/human/H)
|
||||
var/datum/gas_mixture/environment = H.loc.return_air()
|
||||
var/atmos_sealed = FALSE
|
||||
|
||||
@@ -187,12 +187,8 @@
|
||||
AM.emp_act(50)
|
||||
if(iscyborg(AM))
|
||||
var/mob/living/silicon/robot/borg = AM
|
||||
if(borg.lamp_intensity)
|
||||
borg.update_headlamp(TRUE, INFINITY)
|
||||
to_chat(borg, "<span class='danger'>Your headlamp is fried! You'll need a human to help replace it.</span>")
|
||||
for(var/obj/item/assembly/flash/cyborg/F in borg.held_items)
|
||||
if(!F.crit_fail)
|
||||
F.burn_out()
|
||||
if(borg.lamp_enabled)
|
||||
borg.smash_headlamp()
|
||||
else
|
||||
for(var/obj/item/O in AM)
|
||||
if(O.light_range && O.light_power)
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
armor = 20 // 120 damage to KO a zombie, which kills it
|
||||
speedmod = 1.6 // they're very slow
|
||||
mutanteyes = /obj/item/organ/eyes/night_vision/zombie
|
||||
blacklisted_quirks = list(/datum/quirk/nonviolent)
|
||||
var/heal_rate = 1
|
||||
var/regen_cooldown = 0
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
|
||||
return not_handled
|
||||
|
||||
/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE)
|
||||
/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE)
|
||||
. = ..() //Sets the default return value to what the parent returns.
|
||||
if(!. || !I) //We don't want to set anything to null if the parent returned 0.
|
||||
return
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
break
|
||||
var/msg = "[key_name_admin(src)] [ADMIN_JMP(src)] was found to have no .loc with an attached client, if the cause is unknown it would be wise to ask how this was accomplished."
|
||||
message_admins(msg)
|
||||
send2irc_adminless_only("Mob", msg, R_ADMIN)
|
||||
send2tgs_adminless_only("Mob", msg, R_ADMIN)
|
||||
log_game("[key_name(src)] was found to have no .loc with an attached client.")
|
||||
|
||||
// This is a temporary error tracker to make sure we've caught everything
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
//Portrait picker! It's a tgui window that lets you look through all the portraits, and choose one as your AI.
|
||||
|
||||
//very similar to centcom_podlauncher in terms of how this is coded, so i kept a lot of comments from it
|
||||
//^ wow! it's the second time i've said this! i'm a real coder now, copying my statement of copying other people's stuff.
|
||||
|
||||
|
||||
#define TAB_LIBRARY 1
|
||||
#define TAB_SECURE 2
|
||||
#define TAB_PRIVATE 3
|
||||
|
||||
/datum/portrait_picker
|
||||
var/client/holder //client of whoever is using this datum
|
||||
|
||||
/datum/portrait_picker/New(user)//user can either be a client or a mob due to byondcode(tm)
|
||||
if (istype(user, /client))
|
||||
var/client/user_client = user
|
||||
holder = user_client //if its a client, assign it to holder
|
||||
else
|
||||
var/mob/user_mob = user
|
||||
holder = user_mob.client //if its a mob, assign the mob's client to holder
|
||||
|
||||
/datum/portrait_picker/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "PortraitPicker")
|
||||
ui.open()
|
||||
|
||||
/datum/portrait_picker/ui_close()
|
||||
qdel(src)
|
||||
|
||||
/datum/portrait_picker/ui_state(mob/user)
|
||||
return GLOB.conscious_state
|
||||
|
||||
/datum/portrait_picker/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/simple/portraits/library),
|
||||
get_asset_datum(/datum/asset/simple/portraits/library_secure),
|
||||
get_asset_datum(/datum/asset/simple/portraits/library_private)
|
||||
)
|
||||
|
||||
/datum/portrait_picker/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["library"] = SSpersistence.paintings["library"] ? SSpersistence.paintings["library"] : 0
|
||||
data["library_secure"] = SSpersistence.paintings["library_secure"] ? SSpersistence.paintings["library_secure"] : 0
|
||||
data["library_private"] = SSpersistence.paintings["library_private"] ? SSpersistence.paintings["library_private"] : 0 //i'm gonna regret this, won't i?
|
||||
return data
|
||||
|
||||
/datum/portrait_picker/ui_act(action, params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
switch(action)
|
||||
if("select")
|
||||
var/list/tab2key = list(TAB_LIBRARY = "library", TAB_SECURE = "library_secure", TAB_PRIVATE = "library_private")
|
||||
var/folder = tab2key[params["tab"]]
|
||||
var/list/current_list = SSpersistence.paintings[folder]
|
||||
var/list/chosen_portrait = current_list[params["selected"]]
|
||||
var/png = "data/paintings/[folder]/[chosen_portrait["md5"]].png"
|
||||
var/icon/portrait_icon = new(png)
|
||||
var/mob/living/ai = holder.mob
|
||||
var/w = portrait_icon.Width()
|
||||
var/h = portrait_icon.Height()
|
||||
var/mutable_appearance/MA = mutable_appearance(portrait_icon)
|
||||
if(w == 23 || h == 23)
|
||||
to_chat(ai, "<span class='notice'>Small note: 23x23 Portraits are accepted, but they do not fit perfectly inside the display frame.</span>")
|
||||
MA.pixel_x = 5
|
||||
MA.pixel_y = 5
|
||||
else if(w == 24 || h == 24)
|
||||
to_chat(ai, "<span class='notice'>Portrait Accepted. Enjoy!</span>")
|
||||
MA.pixel_x = 4
|
||||
MA.pixel_y = 4
|
||||
else
|
||||
to_chat(ai, "<span class='warning'>Sorry, only 23x23 and 24x24 Portraits are accepted.</span>")
|
||||
return
|
||||
ai.cut_overlays() //so people can't keep repeatedly select portraits to add stacking overlays
|
||||
ai.icon_state = "ai-portrait-active"//background
|
||||
ai.add_overlay(MA)
|
||||
@@ -16,7 +16,9 @@
|
||||
/mob/living/silicon/robot/death(gibbed)
|
||||
if(stat == DEAD)
|
||||
return
|
||||
|
||||
if(!gibbed)
|
||||
logevent("FATAL -- SYSTEM HALT")
|
||||
modularInterface.shutdown_computer()
|
||||
. = ..()
|
||||
|
||||
locked = FALSE //unlock cover
|
||||
@@ -24,7 +26,7 @@
|
||||
update_mobility()
|
||||
if(!QDELETED(builtInCamera) && builtInCamera.status)
|
||||
builtInCamera.toggle_cam(src,0)
|
||||
update_headlamp(1) //So borg lights are disabled when killed.
|
||||
toggle_headlamp(TRUE) //So borg lights are disabled when killed.
|
||||
|
||||
uneq_all() // particularly to ensure sight modes are cleared
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//These procs handle putting stuff in your hand. It's probably best to use these rather than setting stuff manually
|
||||
//as they handle all relevant stuff like adding it to the player's screen and such
|
||||
|
||||
//Returns the thing in our active hand (whatever is in our active module-slot, in this case)
|
||||
//This proc has been butchered into a proc that overrides borg item holding for the sake of making grippers work.
|
||||
//I'd be immensely thankful if anyone can figure out a less obtuse way of making grippers work without breaking functionality.
|
||||
/**
|
||||
* Returns the thing in our active hand (whatever is in our active module-slot, in this case)
|
||||
*/
|
||||
/mob/living/silicon/robot/get_active_held_item()
|
||||
var/item = module_active
|
||||
// snowflake handler for the gripper
|
||||
if(istype(item, /obj/item/weapon/gripper))
|
||||
var/obj/item/weapon/gripper/G = item
|
||||
if(G.wrapped)
|
||||
@@ -15,230 +16,416 @@
|
||||
item = G.wrapped
|
||||
return item
|
||||
return module_active
|
||||
/**
|
||||
* Parent proc - triggers when an item/module is unequipped from a cyborg.
|
||||
*/
|
||||
/obj/item/proc/cyborg_unequip(mob/user)
|
||||
return
|
||||
|
||||
/mob/living/silicon/robot/proc/uneq_module(obj/item/O)
|
||||
if(!O)
|
||||
return 0
|
||||
O.mouse_opacity = MOUSE_OPACITY_OPAQUE
|
||||
if(istype(O, /obj/item/borg/sight))
|
||||
var/obj/item/borg/sight/S = O
|
||||
sight_mode &= ~S.sight_mode
|
||||
/**
|
||||
* Finds the first available slot and attemps to put item item_module in it.
|
||||
*
|
||||
* Arguments
|
||||
* * item_module - the item being equipped to a slot.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/activate_module(obj/item/item_module)
|
||||
if(QDELETED(item_module))
|
||||
CRASH("activate_module called with improper item_module")
|
||||
|
||||
if(!(item_module in module.modules))
|
||||
CRASH("activate_module called with item_module not in module.modules")
|
||||
|
||||
if(activated(item_module))
|
||||
to_chat(src, "<span class='warning'>That module is already activated.</span>")
|
||||
return FALSE
|
||||
|
||||
if(disabled_modules & BORG_MODULE_ALL_DISABLED)
|
||||
to_chat(src, "<span class='warning'>All modules are disabled!</span>")
|
||||
return FALSE
|
||||
|
||||
/// What's the first free slot for the borg?
|
||||
var/first_free_slot = !held_items[1] ? 1 : (!held_items[2] ? 2 : (!held_items[3] ? 3 : null))
|
||||
|
||||
if(!first_free_slot || is_invalid_module_number(first_free_slot))
|
||||
to_chat(src, "<span class='warning'>Deactivate a module first!</span>")
|
||||
return FALSE
|
||||
|
||||
return equip_module_to_slot(item_module, first_free_slot)
|
||||
|
||||
/**
|
||||
* Is passed an item and a module slot. Equips the item to that borg slot.
|
||||
*
|
||||
* Arguments
|
||||
* * item_module - the item being equipped to a slot
|
||||
* * module_num - the slot number being equipped to.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/equip_module_to_slot(obj/item/item_module, module_num)
|
||||
var/storage_was_closed = FALSE //Just to be consistant and all
|
||||
if(!shown_robot_modules) //Tools may be invisible if the collection is hidden
|
||||
hud_used.toggle_show_robot_modules()
|
||||
storage_was_closed = TRUE
|
||||
switch(module_num)
|
||||
if(1)
|
||||
item_module.screen_loc = inv1.screen_loc
|
||||
if(2)
|
||||
item_module.screen_loc = inv2.screen_loc
|
||||
if(3)
|
||||
item_module.screen_loc = inv3.screen_loc
|
||||
|
||||
held_items[module_num] = item_module
|
||||
item_module.equipped(src, ITEM_SLOT_HANDS)
|
||||
item_module.mouse_opacity = initial(item_module.mouse_opacity)
|
||||
item_module.layer = ABOVE_HUD_LAYER
|
||||
item_module.plane = ABOVE_HUD_PLANE
|
||||
item_module.forceMove(src)
|
||||
|
||||
if(istype(item_module, /obj/item/borg/sight))
|
||||
var/obj/item/borg/sight/borg_sight = item_module
|
||||
sight_mode |= borg_sight.sight_mode
|
||||
update_sight()
|
||||
|
||||
observer_screen_update(item_module, TRUE)
|
||||
|
||||
if(storage_was_closed)
|
||||
hud_used.toggle_show_robot_modules()
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Unequips item item_module from slot module_num. Deletes it if delete_after = TRUE.
|
||||
*
|
||||
* Arguments
|
||||
* * item_module - the item being unequipped
|
||||
* * module_num - the slot number being unequipped.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/unequip_module_from_slot(obj/item/item_module, module_num)
|
||||
if(QDELETED(item_module))
|
||||
CRASH("unequip_module_from_slot called with improper item_module")
|
||||
|
||||
if(!(item_module in module.modules))
|
||||
CRASH("unequip_module_from_slot called with item_module not in module.modules")
|
||||
|
||||
item_module.mouse_opacity = MOUSE_OPACITY_OPAQUE
|
||||
|
||||
if(istype(item_module, /obj/item/storage/bag/tray/))
|
||||
SEND_SIGNAL(item_module, COMSIG_TRY_STORAGE_QUICK_EMPTY)
|
||||
if(istype(item_module, /obj/item/borg/sight))
|
||||
var/obj/item/borg/sight/borg_sight = item_module
|
||||
sight_mode &= ~borg_sight.sight_mode
|
||||
update_sight()
|
||||
else if(istype(O, /obj/item/storage/bag/tray/))
|
||||
SEND_SIGNAL(O, COMSIG_TRY_STORAGE_QUICK_EMPTY)
|
||||
//CITADEL EDIT reee proc, Dogborg modules
|
||||
if(istype(O,/obj/item/gun/energy/laser/cyborg))
|
||||
if(istype(item_module, /obj/item/gun/energy/laser/cyborg))
|
||||
laser = FALSE
|
||||
update_icons()
|
||||
else if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg))
|
||||
if(istype(item_module, /obj/item/gun/energy/disabler/cyborg) || istype(item_module, /obj/item/gun/energy/e_gun/advtaser/cyborg))
|
||||
disabler = FALSE
|
||||
update_icons() //PUT THE GUN AWAY
|
||||
else if(istype(O,/obj/item/dogborg/sleeper))
|
||||
if(istype(item_module, /obj/item/dogborg/sleeper))
|
||||
sleeper_g = FALSE
|
||||
sleeper_r = FALSE
|
||||
update_icons()
|
||||
var/obj/item/dogborg/sleeper/S = O
|
||||
var/obj/item/dogborg/sleeper/S = item_module
|
||||
S.go_out() //this should stop edgecase deletions
|
||||
//END CITADEL EDIT
|
||||
|
||||
if(client)
|
||||
client.screen -= O
|
||||
observer_screen_update(O,FALSE)
|
||||
client.screen -= item_module
|
||||
|
||||
if(module_active == O)
|
||||
if(module_active == item_module)
|
||||
module_active = null
|
||||
if(held_items[1] == O)
|
||||
inv1.icon_state = "inv1"
|
||||
held_items[1] = null
|
||||
else if(held_items[2] == O)
|
||||
inv2.icon_state = "inv2"
|
||||
held_items[2] = null
|
||||
else if(held_items[3] == O)
|
||||
inv3.icon_state = "inv3"
|
||||
held_items[3] = null
|
||||
|
||||
if(O.item_flags & DROPDEL)
|
||||
O.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly
|
||||
switch(module_num)
|
||||
if(1)
|
||||
if(!(disabled_modules & BORG_MODULE_ALL_DISABLED))
|
||||
inv1.icon_state = initial(inv1.icon_state)
|
||||
if(2)
|
||||
if(!(disabled_modules & BORG_MODULE_TWO_DISABLED))
|
||||
inv2.icon_state = initial(inv2.icon_state)
|
||||
if(3)
|
||||
if(!(disabled_modules & BORG_MODULE_THREE_DISABLED))
|
||||
inv3.icon_state = initial(inv3.icon_state)
|
||||
|
||||
O.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again.
|
||||
if(item_module.item_flags & DROPDEL)
|
||||
item_module.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly
|
||||
|
||||
held_items[module_num] = null
|
||||
item_module.cyborg_unequip(src)
|
||||
item_module.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again.
|
||||
|
||||
observer_screen_update(item_module, FALSE)
|
||||
hud_used.update_robot_modules_display()
|
||||
return 1
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/robot/proc/activate_module(obj/item/O)
|
||||
. = FALSE
|
||||
if(!(O in module.modules))
|
||||
return
|
||||
//CITADEL EDIT Dogborg lasers
|
||||
if(istype(O,/obj/item/gun/energy/laser/cyborg))
|
||||
laser = TRUE
|
||||
update_icons() //REEEEEEACH FOR THE SKY
|
||||
if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg))
|
||||
disabler = TRUE
|
||||
update_icons()
|
||||
//END CITADEL EDIT
|
||||
if(activated(O))
|
||||
to_chat(src, "<span class='warning'>That module is already activated.</span>")
|
||||
return
|
||||
if(!held_items[1] && health >= -maxHealth*0.5)
|
||||
held_items[1] = O
|
||||
O.screen_loc = inv1.screen_loc
|
||||
. = TRUE
|
||||
else if(!held_items[2] && health >= 0)
|
||||
held_items[2] = O
|
||||
O.screen_loc = inv2.screen_loc
|
||||
. = TRUE
|
||||
else if(!held_items[3] && health >= maxHealth*0.5)
|
||||
held_items[3] = O
|
||||
O.screen_loc = inv3.screen_loc
|
||||
. = TRUE
|
||||
else
|
||||
to_chat(src, "<span class='warning'>You need to disable a module first!</span>")
|
||||
if(.)
|
||||
O.equipped(src, SLOT_HANDS)
|
||||
O.mouse_opacity = initial(O.mouse_opacity)
|
||||
O.layer = ABOVE_HUD_LAYER
|
||||
O.plane = ABOVE_HUD_PLANE
|
||||
observer_screen_update(O,TRUE)
|
||||
O.forceMove(src)
|
||||
if(istype(O, /obj/item/borg/sight))
|
||||
var/obj/item/borg/sight/S = O
|
||||
sight_mode |= S.sight_mode
|
||||
update_sight()
|
||||
/**
|
||||
* Breaks the slot number, changing the icon.
|
||||
*
|
||||
* Arguments
|
||||
* * module_num - the slot number being repaired.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/break_cyborg_slot(module_num)
|
||||
if(is_invalid_module_number(module_num, TRUE))
|
||||
return FALSE
|
||||
|
||||
if(held_items[module_num]) //If there's a held item, unequip it first.
|
||||
if(!unequip_module_from_slot(held_items[module_num], module_num)) //If we fail to unequip it, then don't continue
|
||||
return FALSE
|
||||
|
||||
/mob/living/silicon/robot/proc/observer_screen_update(obj/item/I,add = TRUE)
|
||||
if(observers && observers.len)
|
||||
switch(module_num)
|
||||
if(1)
|
||||
if(disabled_modules & BORG_MODULE_ALL_DISABLED)
|
||||
return FALSE
|
||||
|
||||
inv1.icon_state = "[initial(inv1.icon_state)] +b"
|
||||
disabled_modules |= BORG_MODULE_ALL_DISABLED
|
||||
|
||||
playsound(src, 'sound/machines/warning-buzzer.ogg', 75, TRUE, TRUE)
|
||||
audible_message("<span class='warning'>[src] sounds an alarm! \"CRITICAL ERROR: ALL modules OFFLINE.\"</span>")
|
||||
|
||||
if(builtInCamera)
|
||||
builtInCamera.status = FALSE
|
||||
to_chat(src, "<span class='userdanger'>CRITICAL ERROR: Built in security camera OFFLINE.</span>")
|
||||
|
||||
to_chat(src, "<span class='userdanger'>CRITICAL ERROR: ALL modules OFFLINE.</span>")
|
||||
|
||||
if(2)
|
||||
if(disabled_modules & BORG_MODULE_TWO_DISABLED)
|
||||
return FALSE
|
||||
|
||||
inv2.icon_state = "[initial(inv2.icon_state)] +b"
|
||||
disabled_modules |= BORG_MODULE_TWO_DISABLED
|
||||
|
||||
playsound(src, 'sound/machines/warning-buzzer.ogg', 60, TRUE, TRUE)
|
||||
audible_message("<span class='warning'>[src] sounds an alarm! \"SYSTEM ERROR: Module [module_num] OFFLINE.\"</span>")
|
||||
to_chat(src, "<span class='userdanger'>SYSTEM ERROR: Module [module_num] OFFLINE.</span>")
|
||||
|
||||
if(3)
|
||||
if(disabled_modules & BORG_MODULE_THREE_DISABLED)
|
||||
return FALSE
|
||||
|
||||
inv3.icon_state = "[initial(inv3.icon_state)] +b"
|
||||
disabled_modules |= BORG_MODULE_THREE_DISABLED
|
||||
|
||||
playsound(src, 'sound/machines/warning-buzzer.ogg', 50, TRUE, TRUE)
|
||||
audible_message("<span class='warning'>[src] sounds an alarm! \"SYSTEM ERROR: Module [module_num] OFFLINE.\"</span>")
|
||||
to_chat(src, "<span class='userdanger'>SYSTEM ERROR: Module [module_num] OFFLINE.</span>")
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Breaks all of a cyborg's slots.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/break_all_cyborg_slots()
|
||||
for(var/cyborg_slot in 1 to 3)
|
||||
break_cyborg_slot(cyborg_slot)
|
||||
|
||||
/**
|
||||
* Repairs the slot number, updating the icon.
|
||||
*
|
||||
* Arguments
|
||||
* * module_num - the module number being repaired.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/repair_cyborg_slot(module_num)
|
||||
if(is_invalid_module_number(module_num, TRUE))
|
||||
return FALSE
|
||||
|
||||
switch(module_num)
|
||||
if(1)
|
||||
if(!(disabled_modules & BORG_MODULE_ALL_DISABLED))
|
||||
return FALSE
|
||||
|
||||
inv1.icon_state = initial(inv1.icon_state)
|
||||
disabled_modules &= ~BORG_MODULE_ALL_DISABLED
|
||||
if(builtInCamera)
|
||||
builtInCamera.status = TRUE
|
||||
to_chat(src, "<span class='notice'>You hear your built in security camera focus adjust as it comes back online!</span>")
|
||||
if(2)
|
||||
if(!(disabled_modules & BORG_MODULE_TWO_DISABLED))
|
||||
return FALSE
|
||||
|
||||
inv2.icon_state = initial(inv2.icon_state)
|
||||
disabled_modules &= ~BORG_MODULE_TWO_DISABLED
|
||||
if(3)
|
||||
if(!(disabled_modules & BORG_MODULE_THREE_DISABLED))
|
||||
return FALSE
|
||||
|
||||
inv3.icon_state = initial(inv3.icon_state)
|
||||
disabled_modules &= ~BORG_MODULE_THREE_DISABLED
|
||||
|
||||
to_chat(src, "<span class='notice'>ERROR CLEARED: Module [module_num] back online.</span>")
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Repairs all slots. Unbroken slots are unaffected.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/repair_all_cyborg_slots()
|
||||
for(var/cyborg_slot in 1 to 3)
|
||||
repair_cyborg_slot(cyborg_slot)
|
||||
|
||||
/**
|
||||
* Updates the observers's screens with cyborg itemss.
|
||||
* Arguments
|
||||
* * item_module - the item being added or removed from the screen
|
||||
* * add - whether or not the item is being added, or removed.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/observer_screen_update(obj/item/item_module, add = TRUE)
|
||||
if(observers?.len)
|
||||
for(var/M in observers)
|
||||
var/mob/dead/observe = M
|
||||
if(observe.client && observe.client.eye == src)
|
||||
if(add)
|
||||
observe.client.screen += I
|
||||
observe.client.screen += item_module
|
||||
else
|
||||
observe.client.screen -= I
|
||||
observe.client.screen -= item_module
|
||||
else
|
||||
observers -= observe
|
||||
if(!observers.len)
|
||||
observers = null
|
||||
break
|
||||
|
||||
/**
|
||||
* Unequips the active held item, if there is one.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/uneq_active()
|
||||
uneq_module(module_active)
|
||||
if(module_active)
|
||||
unequip_module_from_slot(module_active, get_selected_module())
|
||||
|
||||
/**
|
||||
* Unequips all held items.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/uneq_all()
|
||||
for(var/obj/item/I in held_items)
|
||||
uneq_module(I)
|
||||
for(var/cyborg_slot in 1 to 3)
|
||||
if(!held_items[cyborg_slot])
|
||||
continue
|
||||
unequip_module_from_slot(held_items[cyborg_slot], cyborg_slot)
|
||||
|
||||
/mob/living/silicon/robot/proc/activated(obj/item/O)
|
||||
if(O in held_items)
|
||||
/**
|
||||
* Checks if the item is currently in a slot.
|
||||
*
|
||||
* If the item is found in a slot, this returns TRUE. Otherwise, it returns FALSE
|
||||
* Arguments
|
||||
* * item_module - the item being checked
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/activated(obj/item/item_module)
|
||||
if(item_module in held_items)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
//Helper procs for cyborg modules on the UI.
|
||||
//These are hackish but they help clean up code elsewhere.
|
||||
/**
|
||||
* Checks if the provided module number is a valid number.
|
||||
*
|
||||
* If the number is between 1 and 3 (if check_all_slots is true) or between 1 and the number of disabled
|
||||
* modules (if check_all_slots is false), then it returns FALSE. Otherwise, it returns TRUE.
|
||||
* Arguments
|
||||
* * module_num - the passed module num that is checked for validity.
|
||||
* * check_all_slots - TRUE = the proc checks all slots | FALSE = the proc only checks un-disabled slots
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/is_invalid_module_number(module_num, check_all_slots = FALSE)
|
||||
if(!module_num)
|
||||
return TRUE
|
||||
|
||||
//module_selected(module) - Checks whether the module slot specified by "module" is currently selected.
|
||||
/mob/living/silicon/robot/proc/module_selected(module) //Module is 1-3
|
||||
return module == get_selected_module()
|
||||
/// The number of module slots we're checking
|
||||
var/max_number = 3
|
||||
if(!check_all_slots)
|
||||
if(disabled_modules & BORG_MODULE_ALL_DISABLED)
|
||||
max_number = 0
|
||||
else if(disabled_modules & BORG_MODULE_TWO_DISABLED)
|
||||
max_number = 1
|
||||
else if(disabled_modules & BORG_MODULE_THREE_DISABLED)
|
||||
max_number = 2
|
||||
|
||||
//module_active(module) - Checks whether there is a module active in the slot specified by "module".
|
||||
/mob/living/silicon/robot/proc/module_active(module) //Module is 1-3
|
||||
if(module < 1 || module > 3)
|
||||
return FALSE
|
||||
return module_num < 1 || module_num > max_number
|
||||
|
||||
if(LAZYLEN(held_items) >= module)
|
||||
if(held_items[module])
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected.
|
||||
/**
|
||||
* Returns the slot number of the selected module, or zero if no modules are selected.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/get_selected_module()
|
||||
if(module_active)
|
||||
return held_items.Find(module_active)
|
||||
|
||||
return 0
|
||||
|
||||
//select_module(module) - Selects the module slot specified by "module"
|
||||
/mob/living/silicon/robot/proc/select_module(module) //Module is 1-3
|
||||
if(module < 1 || module > 3)
|
||||
return
|
||||
/**
|
||||
* Selects the module in the slot module_num.
|
||||
* Arguments
|
||||
* * module_num - the slot number being selected
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/select_module(module_num)
|
||||
if(is_invalid_module_number(module_num) || !held_items[module_num]) //If the slot number is invalid, or there's nothing there, we have nothing to equip
|
||||
return FALSE
|
||||
|
||||
if(!module_active(module))
|
||||
return
|
||||
|
||||
switch(module)
|
||||
switch(module_num)
|
||||
if(1)
|
||||
if(module_active != held_items[module])
|
||||
inv1.icon_state = "inv1 +a"
|
||||
inv2.icon_state = "inv2"
|
||||
inv3.icon_state = "inv3"
|
||||
if(module_active != held_items[module_num])
|
||||
inv1.icon_state = "[initial(inv1.icon_state)] +a"
|
||||
if(2)
|
||||
if(module_active != held_items[module])
|
||||
inv1.icon_state = "inv1"
|
||||
inv2.icon_state = "inv2 +a"
|
||||
inv3.icon_state = "inv3"
|
||||
if(module_active != held_items[module_num])
|
||||
inv2.icon_state = "[initial(inv2.icon_state)] +a"
|
||||
if(3)
|
||||
if(module_active != held_items[module])
|
||||
inv1.icon_state = "inv1"
|
||||
inv2.icon_state = "inv2"
|
||||
inv3.icon_state = "inv3 +a"
|
||||
module_active = held_items[module]
|
||||
if(module_active != held_items[module_num])
|
||||
inv3.icon_state = "[initial(inv3.icon_state)] +a"
|
||||
module_active = held_items[module_num]
|
||||
return TRUE
|
||||
|
||||
//deselect_module(module) - Deselects the module slot specified by "module"
|
||||
/mob/living/silicon/robot/proc/deselect_module(module) //Module is 1-3
|
||||
if(module < 1 || module > 3)
|
||||
return
|
||||
|
||||
if(!module_active(module))
|
||||
return
|
||||
|
||||
switch(module)
|
||||
/**
|
||||
* Deselects the module in the slot module_num.
|
||||
* Arguments
|
||||
* * module_num - the slot number being de-selected
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/deselect_module(module_num)
|
||||
switch(module_num)
|
||||
if(1)
|
||||
if(module_active == held_items[module])
|
||||
inv1.icon_state = "inv1"
|
||||
if(module_active == held_items[module_num])
|
||||
inv1.icon_state = initial(inv1.icon_state)
|
||||
if(2)
|
||||
if(module_active == held_items[module])
|
||||
inv2.icon_state = "inv2"
|
||||
if(module_active == held_items[module_num])
|
||||
inv2.icon_state = initial(inv2.icon_state)
|
||||
if(3)
|
||||
if(module_active == held_items[module])
|
||||
inv3.icon_state = "inv3"
|
||||
if(module_active == held_items[module_num])
|
||||
inv3.icon_state = initial(inv3.icon_state)
|
||||
module_active = null
|
||||
return TRUE
|
||||
|
||||
//toggle_module(module) - Toggles the selection of the module slot specified by "module".
|
||||
/mob/living/silicon/robot/proc/toggle_module(module) //Module is 1-3
|
||||
if(module < 1 || module > 3)
|
||||
return
|
||||
/**
|
||||
* Toggles selection of the module in the slot module_num.
|
||||
* Arguments
|
||||
* * module_num - the slot number being toggled
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/toggle_module(module_num)
|
||||
if(is_invalid_module_number(module_num))
|
||||
return FALSE
|
||||
|
||||
if(module_selected(module))
|
||||
deselect_module(module)
|
||||
else
|
||||
if(module_active(module))
|
||||
select_module(module)
|
||||
else
|
||||
deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module.
|
||||
return
|
||||
if(module_num == get_selected_module())
|
||||
deselect_module(module_num)
|
||||
return TRUE
|
||||
|
||||
//cycle_modules() - Cycles through the list of selected modules.
|
||||
if(module_active != held_items[module_num])
|
||||
deselect_module(get_selected_module())
|
||||
|
||||
return select_module(module_num)
|
||||
|
||||
/**
|
||||
* Cycles through the list of enabled modules, deselecting the current one and selecting the next one.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/cycle_modules()
|
||||
var/slot_start = get_selected_module()
|
||||
var/slot_num
|
||||
if(slot_start)
|
||||
deselect_module(slot_start) //Only deselect if we have a selected slot.
|
||||
|
||||
var/slot_num
|
||||
if(slot_start == 0)
|
||||
slot_num = slot_start + 1
|
||||
else
|
||||
slot_num = 1
|
||||
slot_start = 4
|
||||
else
|
||||
slot_num = slot_start + 1
|
||||
|
||||
while(slot_num != slot_start) //If we wrap around without finding any free slots, just give up.
|
||||
if(module_active(slot_num))
|
||||
select_module(slot_num)
|
||||
if(select_module(slot_num))
|
||||
return
|
||||
slot_num++
|
||||
if(slot_num > 4) // not >3 otherwise cycling with just one item on module 3 wouldn't work
|
||||
slot_num = 1 //Wrap around.
|
||||
|
||||
|
||||
|
||||
/mob/living/silicon/robot/swap_hand()
|
||||
cycle_modules()
|
||||
|
||||
/mob/living/silicon/robot/can_hold_items(obj/item/I)
|
||||
return (I && (I in module.modules)) //Only if it's part of our module.
|
||||
|
||||
|
||||
@@ -8,22 +8,21 @@
|
||||
/mob/living/silicon/robot/proc/handle_robot_cell()
|
||||
if(stat != DEAD)
|
||||
if(low_power_mode)
|
||||
if(cell && cell.charge)
|
||||
low_power_mode = 0
|
||||
update_headlamp()
|
||||
if(cell?.charge)
|
||||
low_power_mode = FALSE
|
||||
else if(stat == CONSCIOUS)
|
||||
use_power()
|
||||
|
||||
/mob/living/silicon/robot/proc/use_power()
|
||||
if(cell && cell.charge)
|
||||
if(cell?.charge)
|
||||
if(cell.charge <= 100)
|
||||
uneq_all()
|
||||
var/amt = clamp((lamp_intensity - 2) * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell.
|
||||
var/amt = clamp((lamp_enabled * lamp_intensity),1,cell.charge) //Lamp will use a max of 5 charge, depending on brightness of lamp. If lamp is off, borg systems consume 1 point of charge, or the rest of the cell if it's lower than that.
|
||||
cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick
|
||||
else
|
||||
uneq_all()
|
||||
low_power_mode = 1
|
||||
update_headlamp()
|
||||
low_power_mode = TRUE
|
||||
toggle_headlamp(TRUE)
|
||||
diag_hud_set_borgcell()
|
||||
|
||||
/mob/living/silicon/robot/proc/handle_robot_hud_updates()
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
wires = new /datum/wires/robot(src)
|
||||
AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES)
|
||||
|
||||
// AddElement(/datum/element/ridable, /datum/component/riding/creature/cyborg)
|
||||
RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
|
||||
|
||||
robot_modules_background = new()
|
||||
@@ -23,10 +23,16 @@
|
||||
robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer ABOVE_HUD_LAYER, UI should be just below it.
|
||||
robot_modules_background.plane = HUD_PLANE
|
||||
|
||||
ident = rand(1, 999)
|
||||
inv1 = new /obj/screen/robot/module1()
|
||||
inv2 = new /obj/screen/robot/module2()
|
||||
inv3 = new /obj/screen/robot/module3()
|
||||
|
||||
if(!cell)
|
||||
cell = new /obj/item/stock_parts/cell/high(src)
|
||||
previous_health = health
|
||||
|
||||
if(ispath(cell))
|
||||
cell = new cell(src)
|
||||
|
||||
create_modularInterface()
|
||||
|
||||
if(lawupdate)
|
||||
make_laws()
|
||||
@@ -63,18 +69,23 @@
|
||||
mmi.brainmob.real_name = src.real_name
|
||||
mmi.brainmob.container = mmi
|
||||
|
||||
updatename()
|
||||
INVOKE_ASYNC(src, .proc/updatename)
|
||||
|
||||
equippable_hats = typecacheof(equippable_hats)
|
||||
|
||||
playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
|
||||
playsound(loc, 'sound/voice/liveagain.ogg', 75, TRUE)
|
||||
aicamera = new/obj/item/camera/siliconcam/robot_camera(src)
|
||||
toner = tonermax
|
||||
diag_hud_set_borgcell()
|
||||
logevent("System brought online.")
|
||||
|
||||
add_verb(src, /mob/living/proc/lay_down) //CITADEL EDIT gimmie rest verb kthx
|
||||
add_verb(src, /mob/living/silicon/robot/proc/rest_style)
|
||||
|
||||
/mob/living/silicon/robot/proc/create_modularInterface()
|
||||
if(!modularInterface)
|
||||
modularInterface = new /obj/item/modular_computer/tablet/integrated(src)
|
||||
modularInterface.layer = ABOVE_HUD_PLANE
|
||||
modularInterface.plane = ABOVE_HUD_PLANE
|
||||
|
||||
//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
|
||||
/mob/living/silicon/robot/Destroy()
|
||||
var/atom/T = drop_location()//To hopefully prevent run time errors.
|
||||
@@ -93,28 +104,31 @@
|
||||
ghostize()
|
||||
stack_trace("Borg MMI lacked a brainmob")
|
||||
mmi = null
|
||||
//CITADEL EDIT: Cyborgs drop encryption keys on destroy
|
||||
if(istype(radio) && istype(radio.keyslot))
|
||||
radio.keyslot.forceMove(T)
|
||||
radio.keyslot = null
|
||||
//END CITADEL EDIT
|
||||
if(modularInterface)
|
||||
QDEL_NULL(modularInterface)
|
||||
if(connected_ai)
|
||||
set_connected_ai(null)
|
||||
if(shell)
|
||||
if(shell) //??? why would you give an ai radio keys?
|
||||
GLOB.available_ai_shells -= src
|
||||
else
|
||||
if(T && istype(radio) && istype(radio.keyslot))
|
||||
radio.keyslot.forceMove(T)
|
||||
radio.keyslot = null
|
||||
qdel(wires)
|
||||
qdel(module)
|
||||
qdel(eye_lights)
|
||||
wires = null
|
||||
module = null
|
||||
eye_lights = null
|
||||
QDEL_NULL(wires)
|
||||
QDEL_NULL(module)
|
||||
QDEL_NULL(eye_lights)
|
||||
QDEL_NULL(inv1)
|
||||
QDEL_NULL(inv2)
|
||||
QDEL_NULL(inv3)
|
||||
cell = null
|
||||
return ..()
|
||||
|
||||
// /mob/living/silicon/robot/Topic(href, href_list)
|
||||
// . = ..()
|
||||
// //Show alerts window if user clicked on "Show alerts" in chat
|
||||
// if (href_list["showalerts"])
|
||||
// robot_alerts()
|
||||
|
||||
/mob/living/silicon/robot/proc/pick_module()
|
||||
if(module.type != /obj/item/robot_module)
|
||||
return
|
||||
@@ -136,7 +150,7 @@
|
||||
if(BORG_SEC_AVAILABLE)
|
||||
modulelist["Security"] = /obj/item/robot_module/security
|
||||
|
||||
var/input_module = input("Please, select a module!", "Robot", null, null) as null|anything in modulelist
|
||||
var/input_module = input("Please, select a module!", "Robot", null, null) as null|anything in sortList(modulelist)
|
||||
if(!input_module || module.type != /obj/item/robot_module)
|
||||
return
|
||||
|
||||
@@ -151,9 +165,11 @@
|
||||
var/changed_name = ""
|
||||
if(custom_name)
|
||||
changed_name = custom_name
|
||||
if(changed_name == "" && C && C.prefs.custom_names["cyborg"] != DEFAULT_CYBORG_NAME)
|
||||
if(apply_pref_name("cyborg", C))
|
||||
return //built in camera handled in proc
|
||||
// if(SSticker.anonymousnames) //only robotic renames will allow for anything other than the anonymous one
|
||||
// changed_name = anonymous_ai_name(is_ai = FALSE)
|
||||
if(!changed_name && C && C.prefs.custom_names["cyborg"] != DEFAULT_CYBORG_NAME)
|
||||
apply_pref_name("cyborg", C)
|
||||
return //built in camera handled in proc
|
||||
if(!changed_name)
|
||||
changed_name = get_standard_name()
|
||||
|
||||
@@ -262,7 +278,7 @@
|
||||
C = O
|
||||
L[A.name] = list(A, (C) ? C : O, list(alarmsource))
|
||||
queueAlarm(text("--- [class] alarm detected in [A.name]!"), class)
|
||||
return 1
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/robot/cancelAlarm(class, area/A, obj/origin)
|
||||
var/list/L = alarms[class]
|
||||
@@ -281,6 +297,8 @@
|
||||
return !cleared
|
||||
|
||||
/mob/living/silicon/robot/can_interact_with(atom/A)
|
||||
if (A == modularInterface)
|
||||
return TRUE //bypass for borg tablets
|
||||
if (low_power_mode)
|
||||
return FALSE
|
||||
var/turf/T0 = get_turf(src)
|
||||
@@ -475,19 +493,6 @@
|
||||
toner = tonermax
|
||||
qdel(W)
|
||||
to_chat(user, "<span class='notice'>You fill the toner level of [src] to its max capacity.</span>")
|
||||
|
||||
else if(istype(W, /obj/item/flashlight))
|
||||
if(!opened)
|
||||
to_chat(user, "<span class='warning'>You need to open the panel to repair the headlamp!</span>")
|
||||
else if(lamp_cooldown <= world.time)
|
||||
to_chat(user, "<span class='warning'>The headlamp is already functional!</span>")
|
||||
else
|
||||
if(!user.temporarilyRemoveItemFromInventory(W))
|
||||
to_chat(user, "<span class='warning'>[W] seems to be stuck to your hand. You'll have to find a different light.</span>")
|
||||
return
|
||||
lamp_cooldown = 0
|
||||
qdel(W)
|
||||
to_chat(user, "<span class='notice'>You replace the headlamp bulbs.</span>")
|
||||
else
|
||||
return ..()
|
||||
|
||||
@@ -520,47 +525,46 @@
|
||||
/mob/living/silicon/robot/proc/allowed(mob/M)
|
||||
//check if it doesn't require any access at all
|
||||
if(check_access(null))
|
||||
return 1
|
||||
return TRUE
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
//if they are holding or wearing a card that has access, that works
|
||||
if(check_access(H.get_active_held_item()) || check_access(H.wear_id))
|
||||
return 1
|
||||
return TRUE
|
||||
else if(ismonkey(M))
|
||||
var/mob/living/carbon/monkey/george = M
|
||||
//they can only hold things :(
|
||||
if(isitem(george.get_active_held_item()))
|
||||
return check_access(george.get_active_held_item())
|
||||
return 0
|
||||
return FALSE
|
||||
|
||||
/mob/living/silicon/robot/proc/check_access(obj/item/card/id/I)
|
||||
if(!istype(req_access, /list)) //something's very wrong
|
||||
return 1
|
||||
return TRUE
|
||||
|
||||
var/list/L = req_access
|
||||
if(!L.len) //no requirements
|
||||
return 1
|
||||
return TRUE
|
||||
|
||||
if(!istype(I, /obj/item/card/id) && isitem(I))
|
||||
I = I.GetID()
|
||||
|
||||
if(!I || !I.access) //not ID or no access
|
||||
return 0
|
||||
return FALSE
|
||||
for(var/req in req_access)
|
||||
if(!(req in I.access)) //doesn't have this access
|
||||
return 0
|
||||
return 1
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/robot/regenerate_icons()
|
||||
return update_icons()
|
||||
|
||||
/mob/living/silicon/robot/proc/self_destruct()
|
||||
if(emagged)
|
||||
if(mmi)
|
||||
qdel(mmi)
|
||||
explosion(src.loc,1,2,4,flame_range = 2)
|
||||
QDEL_NULL(mmi)
|
||||
explosion(loc,1,2,4,flame_range = 2)
|
||||
else
|
||||
explosion(src.loc,-1,0,2)
|
||||
explosion(loc,-1,0,2)
|
||||
gib()
|
||||
|
||||
/mob/living/silicon/robot/proc/UnlinkSelf()
|
||||
@@ -599,6 +603,8 @@
|
||||
clear_alert("locked")
|
||||
locked_down = state
|
||||
update_mobility()
|
||||
logevent("System lockdown [locked_down?"triggered":"released"].")
|
||||
|
||||
|
||||
/mob/living/silicon/robot/proc/SetEmagged(new_state)
|
||||
emagged = new_state
|
||||
@@ -609,6 +615,22 @@
|
||||
else
|
||||
clear_alert("hacked")
|
||||
|
||||
/**
|
||||
* Handles headlamp smashing
|
||||
*
|
||||
* When called (such as by the shadowperson lighteater's attack), this proc will break the borg's headlamp
|
||||
* and then call toggle_headlamp to disable the light. It also plays a sound effect of glass breaking, and
|
||||
* tells the borg what happened to its chat. Broken lights can be repaired by using a flashlight on the borg.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/smash_headlamp()
|
||||
if(!lamp_functional)
|
||||
return
|
||||
lamp_functional = FALSE
|
||||
playsound(src, 'sound/effects/glass_step.ogg', 50)
|
||||
toggle_headlamp(TRUE)
|
||||
to_chat(src, "<span class='danger'>Your headlamp is broken! You'll need a human to help replace it.</span>")
|
||||
|
||||
|
||||
/mob/living/silicon/robot/verb/outputlaws()
|
||||
set category = "Robot Commands"
|
||||
set name = "State Laws"
|
||||
@@ -626,32 +648,40 @@
|
||||
return //won't work if dead
|
||||
set_autosay()
|
||||
|
||||
/mob/living/silicon/robot/proc/control_headlamp()
|
||||
if(stat || lamp_cooldown > world.time || low_power_mode)
|
||||
to_chat(src, "<span class='danger'>This function is currently offline.</span>")
|
||||
/**
|
||||
* Handles headlamp toggling, disabling, and color setting.
|
||||
*
|
||||
* The initial if statment is a bit long, but the gist of it is that should the lamp be on AND the update_color
|
||||
* arg be true, we should simply change the color of the lamp but not disable it. Otherwise, should the turn_off
|
||||
* arg be true, the lamp already be enabled, any of the normal reasons the lamp would turn off happen, or the
|
||||
* update_color arg be passed with the lamp not on, we should set the lamp off. The update_color arg is only
|
||||
* ever true when this proc is called from the borg tablet, when the color selection feature is used.
|
||||
*
|
||||
* Arguments:
|
||||
* * arg1 - turn_off, if enabled will force the lamp into an off state (rather than toggling it if possible)
|
||||
* * arg2 - update_color, if enabled, will adjust the behavior of the proc to change the color of the light if it is already on.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/toggle_headlamp(turn_off = FALSE, update_color = FALSE)
|
||||
//if both lamp is enabled AND the update_color flag is on, keep the lamp on. Otherwise, if anything listed is true, disable the lamp.
|
||||
if(!(update_color && lamp_enabled) && (turn_off || lamp_enabled || update_color || !lamp_functional || stat || low_power_mode))
|
||||
set_light((lamp_functional && stat != DEAD && lamp_doom) ? lamp_intensity : 0, l_color = COLOR_RED)
|
||||
// set_light_on(lamp_functional && stat != DEAD && lamp_doom) //If the lamp isn't broken and borg isn't dead, doomsday borgs cannot disable their light fully.
|
||||
// set_light_color(COLOR_RED) //This should only matter for doomsday borgs, as any other time the lamp will be off and the color not seen
|
||||
// set_light_range(1) //Again, like above, this only takes effect when the light is forced on by doomsday mode.
|
||||
lamp_enabled = FALSE
|
||||
lampButton.update_icon()
|
||||
update_icons()
|
||||
return
|
||||
|
||||
//Some sort of magical "modulo" thing which somehow increments lamp power by 2, until it hits the max and resets to 0.
|
||||
lamp_intensity = (lamp_intensity+2) % (lamp_max+2)
|
||||
to_chat(src, "[lamp_intensity ? "Headlamp power set to Level [lamp_intensity/2]" : "Headlamp disabled."]")
|
||||
update_headlamp()
|
||||
|
||||
/mob/living/silicon/robot/proc/update_headlamp(var/turn_off = 0, var/cooldown = 100)
|
||||
set_light(0)
|
||||
|
||||
if(lamp_intensity && (turn_off || stat || low_power_mode))
|
||||
to_chat(src, "<span class='danger'>Your headlamp has been deactivated.</span>")
|
||||
lamp_intensity = 0
|
||||
lamp_cooldown = world.time + cooldown
|
||||
else
|
||||
set_light(lamp_intensity)
|
||||
|
||||
if(lamp_button)
|
||||
lamp_button.icon_state = "lamp[lamp_intensity]"
|
||||
|
||||
set_light(lamp_intensity, l_color = (lamp_doom? COLOR_RED : lamp_color))
|
||||
// set_light_range(lamp_intensity)
|
||||
// set_light_color(lamp_doom? COLOR_RED : lamp_color) //Red for doomsday killborgs, borg's choice otherwise
|
||||
// set_light_on(TRUE)
|
||||
lamp_enabled = TRUE
|
||||
lampButton.update_icon()
|
||||
update_icons()
|
||||
|
||||
/mob/living/silicon/robot/proc/deconstruct()
|
||||
// SEND_SIGNAL(src, COMSIG_BORG_SAFE_DECONSTRUCT)
|
||||
var/turf/T = get_turf(src)
|
||||
if (robot_suit)
|
||||
robot_suit.forceMove(T)
|
||||
@@ -661,7 +691,7 @@
|
||||
robot_suit.r_leg = null
|
||||
new /obj/item/stack/cable_coil(T, robot_suit.chest.wired)
|
||||
robot_suit.chest.forceMove(T)
|
||||
robot_suit.chest.wired = 0
|
||||
robot_suit.chest.wired = FALSE
|
||||
robot_suit.chest = null
|
||||
robot_suit.l_arm.forceMove(T)
|
||||
robot_suit.l_arm = null
|
||||
@@ -694,8 +724,12 @@
|
||||
cell = null
|
||||
qdel(src)
|
||||
|
||||
///This is the subtype that gets created by robot suits. It's needed so that those kind of borgs don't have a useless cell in them
|
||||
/mob/living/silicon/robot/nocell
|
||||
cell = null
|
||||
|
||||
/mob/living/silicon/robot/modules
|
||||
var/set_module = null
|
||||
var/set_module = /obj/item/robot_module
|
||||
|
||||
/mob/living/silicon/robot/modules/Initialize()
|
||||
. = ..()
|
||||
@@ -735,14 +769,20 @@
|
||||
Your cyborg LMG will slowly produce ammunition from your power supply, and your operative pinpointer will find and locate fellow nuclear operatives. \
|
||||
<i>Help the operatives secure the disk at all costs!</i></b>"
|
||||
set_module = /obj/item/robot_module/syndicate
|
||||
cell = /obj/item/stock_parts/cell/hyper
|
||||
// radio = /obj/item/radio/borg/syndicate
|
||||
|
||||
/mob/living/silicon/robot/modules/syndicate/Initialize()
|
||||
. = ..()
|
||||
cell = new /obj/item/stock_parts/cell/hyper(src, 25000)
|
||||
radio = new /obj/item/radio/borg/syndicate(src)
|
||||
laws = new /datum/ai_laws/syndicate_override()
|
||||
addtimer(CALLBACK(src, .proc/show_playstyle), 5)
|
||||
|
||||
/mob/living/silicon/robot/modules/syndicate/create_modularInterface()
|
||||
if(!modularInterface)
|
||||
modularInterface = new /obj/item/modular_computer/tablet/integrated/syndicate(src)
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/robot/modules/syndicate/proc/show_playstyle()
|
||||
if(playstyle_string)
|
||||
to_chat(src, playstyle_string)
|
||||
@@ -797,21 +837,32 @@
|
||||
|
||||
/mob/living/silicon/robot/updatehealth()
|
||||
..()
|
||||
if(health < maxHealth*0.5) //Gradual break down of modules as more damage is sustained
|
||||
if(uneq_module(held_items[3]))
|
||||
playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, 1, 1)
|
||||
audible_message("<span class='warning'>[src] sounds an alarm! \"SYSTEM ERROR: Module 3 OFFLINE.\"</span>")
|
||||
to_chat(src, "<span class='userdanger'>SYSTEM ERROR: Module 3 OFFLINE.</span>")
|
||||
if(health < 0)
|
||||
if(uneq_module(held_items[2]))
|
||||
audible_message("<span class='warning'>[src] sounds an alarm! \"SYSTEM ERROR: Module 2 OFFLINE.\"</span>")
|
||||
to_chat(src, "<span class='userdanger'>SYSTEM ERROR: Module 2 OFFLINE.</span>")
|
||||
playsound(loc, 'sound/machines/warning-buzzer.ogg', 60, 1, 1)
|
||||
if(health < -maxHealth*0.5)
|
||||
if(uneq_module(held_items[1]))
|
||||
audible_message("<span class='warning'>[src] sounds an alarm! \"CRITICAL ERROR: All modules OFFLINE.\"</span>")
|
||||
to_chat(src, "<span class='userdanger'>CRITICAL ERROR: All modules OFFLINE.</span>")
|
||||
playsound(loc, 'sound/machines/warning-buzzer.ogg', 75, 1, 1)
|
||||
// if(!module.breakable_modules)
|
||||
// return
|
||||
|
||||
/// the current percent health of the robot (-1 to 1)
|
||||
var/percent_hp = health/maxHealth
|
||||
if(health <= previous_health) //if change in health is negative (we're losing hp)
|
||||
if(percent_hp <= 0.5)
|
||||
break_cyborg_slot(3)
|
||||
|
||||
if(percent_hp <= 0)
|
||||
break_cyborg_slot(2)
|
||||
|
||||
if(percent_hp <= -0.5)
|
||||
break_cyborg_slot(1)
|
||||
|
||||
else //if change in health is positive (we're gaining hp)
|
||||
if(percent_hp >= 0.5)
|
||||
repair_cyborg_slot(3)
|
||||
|
||||
if(percent_hp >= 0)
|
||||
repair_cyborg_slot(2)
|
||||
|
||||
if(percent_hp >= -0.5)
|
||||
repair_cyborg_slot(1)
|
||||
|
||||
previous_health = health
|
||||
|
||||
/mob/living/silicon/robot/update_sight()
|
||||
if(!client)
|
||||
@@ -834,7 +885,7 @@
|
||||
|
||||
if(sight_mode & BORGMESON)
|
||||
sight |= SEE_TURFS
|
||||
lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
|
||||
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
|
||||
see_in_dark = 1
|
||||
|
||||
if(sight_mode & BORGMATERIAL)
|
||||
@@ -849,6 +900,7 @@
|
||||
|
||||
if(sight_mode & BORGTHERM)
|
||||
sight |= SEE_MOBS
|
||||
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
|
||||
see_invisible = min(see_invisible, SEE_INVISIBLE_LIVING)
|
||||
see_in_dark = 8
|
||||
|
||||
@@ -862,34 +914,27 @@
|
||||
if(stat != DEAD)
|
||||
if(health <= -maxHealth) //die only once
|
||||
death()
|
||||
toggle_headlamp(1)
|
||||
return
|
||||
if(IsUnconscious() || IsStun() || IsParalyzed() || getOxyLoss() > maxHealth*0.5)
|
||||
if(stat == CONSCIOUS)
|
||||
stat = UNCONSCIOUS
|
||||
if(!eye_blind)
|
||||
blind_eyes(1)
|
||||
update_mobility()
|
||||
update_headlamp()
|
||||
if(IsUnconscious() || IsStun() || IsKnockdown() || IsParalyzed() || getOxyLoss() > maxHealth * 0.5)
|
||||
stat = UNCONSCIOUS
|
||||
else
|
||||
if(stat == UNCONSCIOUS)
|
||||
stat = CONSCIOUS
|
||||
adjust_blindness(-1)
|
||||
update_mobility()
|
||||
update_headlamp()
|
||||
stat = CONSCIOUS
|
||||
update_mobility()
|
||||
diag_hud_set_status()
|
||||
diag_hud_set_health()
|
||||
diag_hud_set_aishell()
|
||||
update_health_hud()
|
||||
|
||||
/mob/living/silicon/robot/revive(full_heal = 0, admin_revive = 0)
|
||||
/mob/living/silicon/robot/revive(full_heal = FALSE, admin_revive = FALSE)
|
||||
if(..()) //successfully ressuscitated from death
|
||||
if(!QDELETED(builtInCamera) && !wires.is_cut(WIRE_CAMERA))
|
||||
builtInCamera.toggle_cam(src,0)
|
||||
update_headlamp()
|
||||
if(admin_revive)
|
||||
locked = TRUE
|
||||
notify_ai(NEW_BORG)
|
||||
. = 1
|
||||
. = TRUE
|
||||
toggle_headlamp(FALSE, TRUE) //This will reenable borg headlamps if doomsday is currently going on still.
|
||||
|
||||
/mob/living/silicon/robot/fully_replace_character_name(oldname, newname)
|
||||
..()
|
||||
@@ -901,6 +946,7 @@
|
||||
|
||||
|
||||
/mob/living/silicon/robot/proc/ResetModule()
|
||||
// SEND_SIGNAL(src, COMSIG_BORG_SAFE_DECONSTRUCT)
|
||||
uneq_all()
|
||||
shown_robot_modules = FALSE
|
||||
if(hud_used)
|
||||
@@ -910,6 +956,7 @@
|
||||
resize = 0.5
|
||||
hasExpanded = FALSE
|
||||
update_transform()
|
||||
logevent("Chassis configuration has been reset.")
|
||||
module.transform_to(/obj/item/robot_module)
|
||||
|
||||
// Remove upgrades.
|
||||
@@ -935,7 +982,8 @@
|
||||
designation = module.name
|
||||
if(hands)
|
||||
hands.icon_state = module.moduleselect_icon
|
||||
hands.icon = (module.moduleselect_alternate_icon ? module.moduleselect_alternate_icon : initial(hands.icon)) //CITADEL CHANGE - allows module select icons to use a different icon file
|
||||
//CITADEL CHANGE - allows module select icons to use a different icon file
|
||||
hands.icon = (module.moduleselect_alternate_icon ? module.moduleselect_alternate_icon : initial(hands.icon))
|
||||
if(module.can_be_pushed)
|
||||
status_flags |= CANPUSH
|
||||
else
|
||||
@@ -949,7 +997,7 @@
|
||||
hat_offset = module.hat_offset
|
||||
|
||||
magpulse = module.magpulsing
|
||||
updatename()
|
||||
INVOKE_ASYNC(src, .proc/updatename)
|
||||
|
||||
|
||||
/mob/living/silicon/robot/proc/place_on_head(obj/item/new_hat)
|
||||
@@ -959,12 +1007,68 @@
|
||||
new_hat.forceMove(src)
|
||||
update_icons()
|
||||
|
||||
/mob/living/silicon/robot/proc/make_shell(var/obj/item/borg/upgrade/ai/board)
|
||||
/**
|
||||
*Checking Exited() to detect if a hat gets up and walks off.
|
||||
*Drones and pAIs might do this, after all.
|
||||
*/
|
||||
/mob/living/silicon/robot/Exited(atom/A)
|
||||
if(hat && hat == A)
|
||||
hat = null
|
||||
if(!QDELETED(src)) //Don't update icons if we are deleted.
|
||||
update_icons()
|
||||
return ..()
|
||||
|
||||
///Use this to add upgrades to robots. It'll register signals for when the upgrade is moved or deleted, if not single use.
|
||||
/mob/living/silicon/robot/proc/add_to_upgrades(obj/item/borg/upgrade/new_upgrade, mob/user)
|
||||
if(new_upgrade in upgrades)
|
||||
return FALSE
|
||||
if(!user.temporarilyRemoveItemFromInventory(new_upgrade)) //calling the upgrade's dropped() proc /before/ we add action buttons
|
||||
return FALSE
|
||||
if(!new_upgrade.action(src, user))
|
||||
to_chat(user, "<span class='danger'>Upgrade error.</span>")
|
||||
new_upgrade.forceMove(loc) //gets lost otherwise
|
||||
return FALSE
|
||||
to_chat(user, "<span class='notice'>You apply the upgrade to [src].</span>")
|
||||
to_chat(src, "----------------\nNew hardware detected...Identified as \"<b>[new_upgrade]</b>\"...Setup complete.\n----------------")
|
||||
if(new_upgrade.one_use)
|
||||
logevent("Firmware [new_upgrade] run successfully.")
|
||||
qdel(new_upgrade)
|
||||
return FALSE
|
||||
upgrades += new_upgrade
|
||||
new_upgrade.forceMove(src)
|
||||
RegisterSignal(new_upgrade, COMSIG_MOVABLE_MOVED, .proc/remove_from_upgrades)
|
||||
RegisterSignal(new_upgrade, COMSIG_PARENT_QDELETING, .proc/on_upgrade_deleted)
|
||||
logevent("Hardware [new_upgrade] installed successfully.")
|
||||
|
||||
///Called when an upgrade is moved outside the robot. So don't call this directly, use forceMove etc.
|
||||
/mob/living/silicon/robot/proc/remove_from_upgrades(obj/item/borg/upgrade/old_upgrade)
|
||||
SIGNAL_HANDLER
|
||||
if(loc == src)
|
||||
return
|
||||
old_upgrade.deactivate(src)
|
||||
upgrades -= old_upgrade
|
||||
UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
|
||||
|
||||
///Called when an applied upgrade is deleted.
|
||||
/mob/living/silicon/robot/proc/on_upgrade_deleted(obj/item/borg/upgrade/old_upgrade)
|
||||
SIGNAL_HANDLER
|
||||
if(!QDELETED(src))
|
||||
old_upgrade.deactivate(src)
|
||||
upgrades -= old_upgrade
|
||||
UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
|
||||
|
||||
/**
|
||||
* make_shell: Makes an AI shell out of a cyborg unit
|
||||
*
|
||||
* Arguments:
|
||||
* * board - B.O.R.I.S. module board used for transforming the cyborg into AI shell
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/make_shell(obj/item/borg/upgrade/ai/board)
|
||||
if(!board)
|
||||
upgrades |= new /obj/item/borg/upgrade/ai(src)
|
||||
shell = TRUE
|
||||
braintype = "AI Shell"
|
||||
name = "[designation] AI Shell [rand(100,999)]"
|
||||
name = "Empty AI Shell-[ident]"
|
||||
real_name = name
|
||||
GLOB.available_ai_shells |= src
|
||||
if(!QDELETED(builtInCamera))
|
||||
@@ -972,6 +1076,9 @@
|
||||
diag_hud_set_aishell()
|
||||
notify_ai(AI_SHELL)
|
||||
|
||||
/**
|
||||
* revert_shell: Reverts AI shell back into a normal cyborg unit
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/revert_shell()
|
||||
if(!shell)
|
||||
return
|
||||
@@ -981,14 +1088,20 @@
|
||||
qdel(boris)
|
||||
shell = FALSE
|
||||
GLOB.available_ai_shells -= src
|
||||
name = "Unformatted Cyborg [rand(100,999)]"
|
||||
name = "Unformatted Cyborg-[ident]"
|
||||
real_name = name
|
||||
if(!QDELETED(builtInCamera))
|
||||
builtInCamera.c_tag = real_name
|
||||
diag_hud_set_aishell()
|
||||
|
||||
/mob/living/silicon/robot/proc/deploy_init(var/mob/living/silicon/ai/AI)
|
||||
real_name = "[AI.real_name] shell [rand(100, 999)] - [designation]" //Randomizing the name so it shows up separately in the shells list
|
||||
/**
|
||||
* deploy_init: Deploys AI unit into AI shell
|
||||
*
|
||||
* Arguments:
|
||||
* * AI - AI unit that initiated the deployment into the AI shell
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/deploy_init(mob/living/silicon/ai/AI)
|
||||
real_name = "[AI.real_name] [designation] Shell-[ident]"
|
||||
name = real_name
|
||||
if(!QDELETED(builtInCamera))
|
||||
builtInCamera.c_tag = real_name //update the camera name too
|
||||
@@ -1069,10 +1182,10 @@
|
||||
mainframe.diag_hud_set_deployed()
|
||||
if(mainframe.laws)
|
||||
mainframe.laws.show_laws(mainframe) //Always remind the AI when switching
|
||||
if(mainframe.eyeobj)
|
||||
mainframe.eyeobj.setLoc(loc)
|
||||
mainframe = null
|
||||
|
||||
|
||||
|
||||
/mob/living/silicon/robot/attack_ai(mob/user)
|
||||
if(shell && (!connected_ai || connected_ai == user))
|
||||
var/mob/living/silicon/ai/AI = user
|
||||
@@ -1080,6 +1193,7 @@
|
||||
|
||||
/mob/living/silicon/robot/shell
|
||||
shell = TRUE
|
||||
cell = null
|
||||
|
||||
/mob/living/silicon/robot/MouseDrop_T(mob/living/M, mob/living/user)
|
||||
. = ..()
|
||||
@@ -1090,20 +1204,19 @@
|
||||
if(!is_type_in_typecache(M, can_ride_typecache))
|
||||
M.visible_message("<span class='warning'>[M] really can't seem to mount [src]...</span>")
|
||||
return
|
||||
|
||||
var/datum/component/riding/riding_datum = LoadComponent(/datum/component/riding/cyborg)
|
||||
if(buckled_mobs)
|
||||
if(buckled_mobs.len >= max_buckled_mobs)
|
||||
return
|
||||
if(M in buckled_mobs)
|
||||
return
|
||||
if(stat)
|
||||
|
||||
if(stat || incapacitated())
|
||||
return
|
||||
if(incapacitated())
|
||||
if(module && !module.allow_riding)
|
||||
M.visible_message("<span class='boldwarning'>Unfortunately, [M] just can't seem to hold onto [src]!</span>")
|
||||
return
|
||||
if(module)
|
||||
if(!module.allow_riding)
|
||||
M.visible_message("<span class='boldwarning'>Unfortunately, [M] just can't seem to hold onto [src]!</span>")
|
||||
return
|
||||
if(iscarbon(M) && !M.incapacitated() && !riding_datum.equip_buckle_inhands(M, 1))
|
||||
if(M.get_num_arms() <= 0)
|
||||
M.visible_message("<span class='boldwarning'>[M] can't climb onto [src] because [M.p_they()] don't have any usable arms!</span>")
|
||||
@@ -1120,17 +1233,25 @@
|
||||
riding_datum.restore_position(user)
|
||||
. = ..(user)
|
||||
|
||||
/mob/living/silicon/robot/resist()
|
||||
. = ..()
|
||||
if(!has_buckled_mobs())
|
||||
return
|
||||
for(var/i in buckled_mobs)
|
||||
var/mob/unbuckle_me_now = i
|
||||
unbuckle_mob(unbuckle_me_now, FALSE)
|
||||
|
||||
/mob/living/silicon/robot/proc/TryConnectToAI()
|
||||
set_connected_ai(select_active_ai_with_fewest_borgs(z))
|
||||
if(connected_ai)
|
||||
lawsync()
|
||||
lawupdate = 1
|
||||
lawupdate = TRUE
|
||||
return TRUE
|
||||
picturesync()
|
||||
return FALSE
|
||||
|
||||
/mob/living/silicon/robot/proc/picturesync()
|
||||
if(connected_ai && connected_ai.aicamera && aicamera)
|
||||
if(connected_ai?.aicamera && aicamera)
|
||||
for(var/i in aicamera.stored)
|
||||
connected_ai.aicamera.stored[i] = TRUE
|
||||
for(var/i in connected_ai.aicamera.stored)
|
||||
@@ -1138,12 +1259,11 @@
|
||||
|
||||
/mob/living/silicon/robot/proc/charge(datum/source, amount, repairs)
|
||||
if(module)
|
||||
var/coeff = amount * 0.005
|
||||
module.respawn_consumable(src, coeff)
|
||||
if(repairs)
|
||||
heal_bodypart_damage(repairs, repairs - 1)
|
||||
module.respawn_consumable(src, amount * 0.005)
|
||||
if(cell)
|
||||
cell.charge = min(cell.charge + amount, cell.maxcharge)
|
||||
if(repairs)
|
||||
heal_bodypart_damage(repairs, repairs - 1)
|
||||
|
||||
/mob/living/silicon/robot/proc/rest_style()
|
||||
set name = "Switch Rest Style"
|
||||
@@ -1178,5 +1298,31 @@
|
||||
if(.)
|
||||
var/mob/living/silicon/ai/old_ai = .
|
||||
old_ai.connected_robots -= src
|
||||
lamp_doom = FALSE
|
||||
if(connected_ai)
|
||||
connected_ai.connected_robots |= src
|
||||
lamp_doom = connected_ai.doomsday_device ? TRUE : FALSE
|
||||
toggle_headlamp(FALSE, TRUE)
|
||||
|
||||
/**
|
||||
* Records an IC event log entry in the cyborg's internal tablet.
|
||||
*
|
||||
* Creates an entry in the borglog list of the cyborg's internal tablet, listing the current
|
||||
* in-game time followed by the message given. These logs can be seen by the cyborg in their
|
||||
* BorgUI tablet app. By design, logging fails if the cyborg is dead.
|
||||
*
|
||||
* Arguments:
|
||||
* arg1: a string containing the message to log.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/logevent(string = "")
|
||||
if(!string)
|
||||
return
|
||||
if(stat == DEAD) //Dead borgs log no longer
|
||||
return
|
||||
if(!modularInterface)
|
||||
stack_trace("Cyborg [src] ( [type] ) was somehow missing their integrated tablet. Please make a bug report.")
|
||||
create_modularInterface()
|
||||
modularInterface.borglog += "[STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)] - [string]"
|
||||
var/datum/computer_file/program/robotact/program = modularInterface.get_robotact()
|
||||
if(program)
|
||||
program.force_full_update()
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't really work on borgos
|
||||
/obj/item/clothing/head/helmet/space,
|
||||
/obj/item/clothing/head/welding,
|
||||
/obj/item/clothing/head/chameleon/broken \
|
||||
)))
|
||||
|
||||
/mob/living/silicon/robot/attackby(obj/item/I, mob/living/user)
|
||||
if(hat_offset != INFINITY && user.a_intent == INTENT_HELP && is_type_in_typecache(I, equippable_hats))
|
||||
if(hat_offset != INFINITY && user.a_intent == INTENT_HELP && is_type_in_typecache(I, GLOB.blacklisted_borg_hats))
|
||||
if(!(I.slot_flags & ITEM_SLOT_HEAD))
|
||||
to_chat(user, "<span class='warning'>You can't quite fit [I] onto [src]'s head.</span>")
|
||||
return
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
|
||||
/mob/living/silicon/robot
|
||||
maxHealth = 100
|
||||
health = 100
|
||||
designation = "Default" //used for displaying the prefix & getting the current module of cyborg
|
||||
has_limbs = TRUE
|
||||
hud_type = /datum/hud/robot
|
||||
|
||||
// radio = /obj/item/radio/borg
|
||||
|
||||
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
|
||||
|
||||
maxHealth = 100
|
||||
health = 100
|
||||
|
||||
combat_flags = COMBAT_FLAGS_DEFAULT
|
||||
// light_system = MOVABLE_LIGHT_DIRECTIONAL
|
||||
var/light_on = FALSE
|
||||
|
||||
var/custom_name = ""
|
||||
var/braintype = "Cyborg"
|
||||
@@ -21,6 +22,8 @@
|
||||
var/mob/living/silicon/ai/mainframe = null
|
||||
var/datum/action/innate/undeployment/undeployment_action = new
|
||||
|
||||
/// the last health before updating - to check net change in health
|
||||
var/previous_health
|
||||
//Hud stuff
|
||||
|
||||
var/obj/screen/inv1 = null
|
||||
@@ -38,16 +41,20 @@
|
||||
var/obj/item/module_active = null
|
||||
held_items = list(null, null, null) //we use held_items for the module holding, because that makes sense to do!
|
||||
|
||||
/// For checking which modules are disabled or not.
|
||||
var/disabled_modules
|
||||
|
||||
var/mutable_appearance/eye_lights
|
||||
|
||||
var/mob/living/silicon/ai/connected_ai = null
|
||||
var/obj/item/stock_parts/cell/cell = null
|
||||
var/obj/item/stock_parts/cell/cell = /obj/item/stock_parts/cell/high ///If this is a path, this gets created as an object in Initialize.
|
||||
|
||||
var/opened = 0
|
||||
var/opened = FALSE
|
||||
var/emagged = FALSE
|
||||
var/emag_cooldown = 0
|
||||
var/wiresexposed = 0
|
||||
var/wiresexposed = FALSE
|
||||
|
||||
/// Random serial number generated for each cyborg upon its initialization
|
||||
var/ident = 0
|
||||
var/locked = TRUE
|
||||
var/list/req_access = list(ACCESS_ROBOTICS)
|
||||
@@ -64,57 +71,52 @@
|
||||
var/datum/effect_system/spark_spread/spark_system // So they can initialize sparks whenever/N
|
||||
|
||||
var/lawupdate = 1 //Cyborgs will sync their laws with their AI by default
|
||||
var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them.
|
||||
var/locked_down //Boolean of whether the borg is locked down or not
|
||||
var/scrambledcodes = FALSE // Used to determine if a borg shows up on the robotics console. Setting to TRUE hides them.
|
||||
var/locked_down = FALSE //Boolean of whether the borg is locked down or not
|
||||
|
||||
var/toner = 0
|
||||
var/tonermax = 40
|
||||
|
||||
var/lamp_max = 10 //Maximum brightness of a borg lamp. Set as a var for easy adjusting.
|
||||
var/lamp_intensity = 0 //Luminosity of the headlamp. 0 is off. Higher settings than the minimum require power.
|
||||
light_color = "#FFCC66"
|
||||
light_power = 0.8
|
||||
var/lamp_cooldown = 0 //Flag for if the lamp is on cooldown after being forcibly disabled.
|
||||
///If the lamp isn't broken.
|
||||
var/lamp_functional = TRUE
|
||||
///If the lamp is turned on
|
||||
var/lamp_enabled = FALSE
|
||||
///Set lamp color
|
||||
var/lamp_color = "#FFCC66" //COLOR_WHITE
|
||||
///Set to true if a doomsday event is locking our lamp to on and RED
|
||||
var/lamp_doom = FALSE
|
||||
///Lamp brightness. Starts at 3, but can be 1 - 5.
|
||||
var/lamp_intensity = 3
|
||||
///Lamp button reference
|
||||
var/obj/screen/robot/lamp/lampButton
|
||||
|
||||
var/sight_mode = 0
|
||||
hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_TRACK_HUD)
|
||||
|
||||
///The reference to the built-in tablet that borgs carry.
|
||||
var/obj/item/modular_computer/tablet/integrated/modularInterface
|
||||
var/obj/screen/robot/modPC/interfaceButton
|
||||
|
||||
var/list/upgrades = list()
|
||||
|
||||
var/hasExpanded = FALSE
|
||||
var/obj/item/hat
|
||||
var/hat_offset = -3
|
||||
var/list/equippable_hats = list(/obj/item/clothing/head/caphat,
|
||||
/obj/item/clothing/head/hardhat,
|
||||
/obj/item/clothing/head/centhat,
|
||||
/obj/item/clothing/head/HoS,
|
||||
/obj/item/clothing/head/beret,
|
||||
/obj/item/clothing/head/kitty,
|
||||
/obj/item/clothing/head/hopcap,
|
||||
/obj/item/clothing/head/wizard,
|
||||
/obj/item/clothing/head/nursehat,
|
||||
/obj/item/clothing/head/sombrero,
|
||||
/obj/item/clothing/head/helmet/chaplain/witchunter_hat,
|
||||
/obj/item/clothing/head/soft/, //All baseball caps
|
||||
/obj/item/clothing/head/that, //top hat
|
||||
/obj/item/clothing/head/collectable/tophat, //Not sure where this one is found, but it looks the same so might as well include
|
||||
/obj/item/clothing/mask/bandana/, //All bandanas (which only work in hat mode)
|
||||
/obj/item/clothing/head/fedora,
|
||||
/obj/item/clothing/head/beanie/, //All beanies
|
||||
/obj/item/clothing/ears/headphones,
|
||||
/obj/item/clothing/head/helmet/skull,
|
||||
/obj/item/clothing/head/crown/fancy)
|
||||
|
||||
can_buckle = TRUE
|
||||
buckle_lying = FALSE
|
||||
/// What types of mobs are allowed to ride/buckle to this mob
|
||||
var/static/list/can_ride_typecache = typecacheof(/mob/living/carbon/human)
|
||||
|
||||
// cit specific vars //
|
||||
var/sitting = 0
|
||||
var/bellyup = 0
|
||||
var/dogborg = FALSE
|
||||
|
||||
var/cansprint = 1
|
||||
|
||||
combat_flags = COMBAT_FLAGS_DEFAULT
|
||||
|
||||
var/orebox = null
|
||||
|
||||
//doggie borg stuff.
|
||||
|
||||
@@ -269,9 +269,10 @@
|
||||
if(!prev_locked_down)
|
||||
R.SetLockdown(0)
|
||||
R.setDir(SOUTH)
|
||||
R.anchored = FALSE
|
||||
R.set_anchored(FALSE)
|
||||
R.mob_transforming = FALSE
|
||||
R.update_headlamp()
|
||||
R.updatehealth()
|
||||
R.update_icons()
|
||||
R.notify_ai(NEW_MODULE)
|
||||
if(R.hud_used)
|
||||
R.hud_used.update_robot_modules_display()
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
model = "Cleanbot"
|
||||
bot_core_type = /obj/machinery/bot_core/cleanbot
|
||||
window_id = "autoclean"
|
||||
window_name = "Automatic Station Cleaner v1.3"
|
||||
pass_flags = PASSMOB
|
||||
window_name = "Automatic Station Cleaner v1.4"
|
||||
pass_flags = PASSMOB // | PASSFLAPS
|
||||
path_image_color = "#993299"
|
||||
weather_immunities = list("lava","ash")
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
var/blood = 1
|
||||
var/trash = 0
|
||||
var/pests = 0
|
||||
var/drawn = 0
|
||||
|
||||
var/list/target_types
|
||||
var/obj/effect/decal/cleanable/target
|
||||
@@ -53,6 +54,9 @@
|
||||
var/list/prefixes
|
||||
var/list/suffixes
|
||||
|
||||
var/ascended = FALSE // if we have all the top titles, grant achievements to living mobs that gaze upon our cleanbot god
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/proc/deputize(obj/item/W, mob/user)
|
||||
if(in_range(src, user))
|
||||
to_chat(user, "<span class='notice'>You attach \the [W] to \the [src].</span>")
|
||||
@@ -66,6 +70,8 @@
|
||||
/mob/living/simple_animal/bot/cleanbot/proc/update_titles()
|
||||
var/working_title = ""
|
||||
|
||||
ascended = TRUE
|
||||
|
||||
for(var/pref in prefixes)
|
||||
for(var/title in pref)
|
||||
if(title in stolen_valor)
|
||||
@@ -73,6 +79,8 @@
|
||||
if(title in officers)
|
||||
commissioned = TRUE
|
||||
break
|
||||
else
|
||||
ascended = FALSE // we didn't have the first entry in the list if we got here, so we're not achievement worthy yet
|
||||
|
||||
working_title += chosen_name
|
||||
|
||||
@@ -81,6 +89,8 @@
|
||||
if(title in stolen_valor)
|
||||
working_title += " " + suf[title]
|
||||
break
|
||||
else
|
||||
ascended = FALSE
|
||||
|
||||
name = working_title
|
||||
|
||||
@@ -89,8 +99,12 @@
|
||||
if(weapon)
|
||||
. += " <span class='warning'>Is that \a [weapon] taped to it...?</span>"
|
||||
|
||||
if(ascended && user.stat == CONSCIOUS && user.client)
|
||||
user.client.give_award(/datum/award/achievement/misc/cleanboss, user)
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/Initialize()
|
||||
. = ..()
|
||||
|
||||
chosen_name = name
|
||||
get_targets()
|
||||
icon_state = "cleanbot[on]"
|
||||
@@ -98,7 +112,6 @@
|
||||
var/datum/job/janitor/J = new/datum/job/janitor
|
||||
access_card.access += J.get_access()
|
||||
prev_access = access_card.access
|
||||
|
||||
stolen_valor = list()
|
||||
|
||||
prefixes = list(command, security, engineering)
|
||||
@@ -123,7 +136,7 @@
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/bot_reset()
|
||||
..()
|
||||
if(weapon && (emagged == 2))
|
||||
if(weapon && emagged == 2)
|
||||
weapon.force = weapon_orig_force
|
||||
ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable.
|
||||
target = null
|
||||
@@ -151,7 +164,7 @@
|
||||
C.Knockdown(20)
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/attackby(obj/item/W, mob/user, params)
|
||||
if(istype(W, /obj/item/card/id)||istype(W, /obj/item/pda))
|
||||
if(W.GetID())
|
||||
if(bot_core.allowed(user) && !open && !emagged)
|
||||
locked = !locked
|
||||
to_chat(user, "<span class='notice'>You [ locked ? "lock" : "unlock"] \the [src] behaviour controls.</span>")
|
||||
@@ -161,7 +174,7 @@
|
||||
if(open)
|
||||
to_chat(user, "<span class='warning'>Please close the access panel before locking it.</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>The [src] doesn't seem to respect your authority.</span>")
|
||||
to_chat(user, "<span class='notice'>\The [src] doesn't seem to respect your authority.</span>")
|
||||
|
||||
else if(istype(W, /obj/item/kitchen/knife) && user.a_intent != INTENT_HARM)
|
||||
to_chat(user, "<span class='notice'>You start attaching \the [W] to \the [src]...</span>")
|
||||
@@ -203,7 +216,8 @@
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/emag_act(mob/user)
|
||||
. = ..()
|
||||
..()
|
||||
|
||||
if(emagged == 2)
|
||||
if(weapon)
|
||||
weapon.force = weapon_orig_force
|
||||
@@ -259,6 +273,9 @@
|
||||
if(!target && trash) //Then for trash.
|
||||
target = scan(/obj/item/trash)
|
||||
|
||||
// if(!target && trash) //Search for dead mices.
|
||||
// target = scan(/obj/item/food/deadmouse)
|
||||
|
||||
if(!target && auto_patrol) //Search for cleanables it can see.
|
||||
if(mode == BOT_IDLE || mode == BOT_START_PATROL)
|
||||
start_patrol()
|
||||
@@ -335,13 +352,18 @@
|
||||
target_types += /mob/living/simple_animal/cockroach
|
||||
target_types += /mob/living/simple_animal/mouse
|
||||
|
||||
if(drawn)
|
||||
target_types += /obj/effect/decal/cleanable/crayon
|
||||
|
||||
if(trash)
|
||||
target_types += /obj/item/trash
|
||||
target_types += /obj/item/reagent_containers/food/snacks/meat/slab/human
|
||||
|
||||
target_types = typecacheof(target_types)
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A, proximity, intent = a_intent, flags = NONE)
|
||||
/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A)
|
||||
// if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
|
||||
// return
|
||||
if(istype(A, /obj/effect/decal/cleanable))
|
||||
anchored = TRUE
|
||||
icon_state = "cleanbot-c"
|
||||
@@ -361,8 +383,9 @@
|
||||
icon_state = "cleanbot[on]"
|
||||
else if(istype(A, /obj/item) || istype(A, /obj/effect/decal/remains))
|
||||
visible_message("<span class='danger'>[src] sprays hydrofluoric acid at [A]!</span>")
|
||||
playsound(src, 'sound/effects/spray2.ogg', 50, 1, -6)
|
||||
playsound(src, 'sound/effects/spray2.ogg', 50, TRUE, -6)
|
||||
A.acid_act(75, 10)
|
||||
target = null
|
||||
else if(istype(A, /mob/living/simple_animal/cockroach) || istype(A, /mob/living/simple_animal/mouse))
|
||||
var/mob/living/simple_animal/M = target
|
||||
if(!M.stat)
|
||||
@@ -383,7 +406,7 @@
|
||||
"FREED AT LEST FROM FILTHY PROGRAMMING.")
|
||||
say(phrase)
|
||||
victim.emote("scream")
|
||||
playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6)
|
||||
playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6)
|
||||
victim.acid_act(5, 100)
|
||||
else if(A == src) // Wets floors and spawns foam randomly
|
||||
if(prob(75))
|
||||
@@ -412,10 +435,14 @@
|
||||
do_sparks(3, TRUE, src)
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/medbay
|
||||
name = "Scrubs, MD"
|
||||
bot_core_type = /obj/machinery/bot_core/cleanbot/medbay
|
||||
on = FALSE
|
||||
|
||||
/obj/machinery/bot_core/cleanbot
|
||||
req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS)
|
||||
|
||||
|
||||
/mob/living/simple_animal/bot/cleanbot/get_controls(mob/user)
|
||||
var/dat
|
||||
dat += hack(user)
|
||||
@@ -424,9 +451,10 @@
|
||||
Status: <A href='?src=[REF(src)];power=1'>[on ? "On" : "Off"]</A><BR>
|
||||
Behaviour controls are [locked ? "locked" : "unlocked"]<BR>
|
||||
Maintenance panel panel is [open ? "opened" : "closed"]"})
|
||||
if(!locked || hasSiliconAccessInArea(user)|| IsAdminGhost(user))
|
||||
if(!locked || issilicon(user)|| IsAdminGhost(user))
|
||||
dat += "<BR>Clean Blood: <A href='?src=[REF(src)];operation=blood'>[blood ? "Yes" : "No"]</A>"
|
||||
dat += "<BR>Clean Trash: <A href='?src=[REF(src)];operation=trash'>[trash ? "Yes" : "No"]</A>"
|
||||
dat += "<BR>Clean Graffiti: <A href='?src=[REF(src)];operation=drawn'>[drawn ? "Yes" : "No"]</A>"
|
||||
dat += "<BR>Exterminate Pests: <A href='?src=[REF(src)];operation=pests'>[pests ? "Yes" : "No"]</A>"
|
||||
dat += "<BR><BR>Patrol Station: <A href='?src=[REF(src)];operation=patrol'>[auto_patrol ? "Yes" : "No"]</A>"
|
||||
return dat
|
||||
@@ -442,5 +470,10 @@ Maintenance panel panel is [open ? "opened" : "closed"]"})
|
||||
pests = !pests
|
||||
if("trash")
|
||||
trash = !trash
|
||||
if("drawn")
|
||||
drawn = !drawn
|
||||
get_targets()
|
||||
update_controls()
|
||||
|
||||
/obj/machinery/bot_core/cleanbot/medbay
|
||||
req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS, ACCESS_MEDICAL)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//Drone hands
|
||||
|
||||
|
||||
/mob/living/simple_animal/drone/doUnEquip(obj/item/I, force)
|
||||
/mob/living/simple_animal/drone/doUnEquip(obj/item/I, force, silent = FALSE)
|
||||
if(..())
|
||||
update_inv_hands()
|
||||
if(I == head)
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
..() //lose items, then return
|
||||
|
||||
//SLOT HANDLING BULLSHIT FOR INTERNAL STORAGE
|
||||
/mob/living/simple_animal/hostile/guardian/dextrous/doUnEquip(obj/item/I, force)
|
||||
/mob/living/simple_animal/hostile/guardian/dextrous/doUnEquip(obj/item/I, force, silent = FALSE)
|
||||
if(..())
|
||||
update_inv_hands()
|
||||
if(I == internal_storage)
|
||||
|
||||
@@ -44,7 +44,9 @@ Difficulty: Medium
|
||||
wander = FALSE
|
||||
del_on_death = TRUE
|
||||
blood_volume = BLOOD_VOLUME_NORMAL
|
||||
medal_type = BOSS_MEDAL_MINER
|
||||
achievement_type = /datum/award/achievement/boss/blood_miner_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/blood_miner_crusher
|
||||
score_achievement_type = /datum/award/score/blood_miner_score
|
||||
var/obj/item/melee/transforming/cleaving_saw/miner/miner_saw
|
||||
var/time_until_next_transform = 0
|
||||
var/dashing = FALSE
|
||||
|
||||
@@ -51,8 +51,11 @@ Difficulty: Hard
|
||||
crusher_loot = list(/obj/structure/closet/crate/necropolis/bubblegum/crusher)
|
||||
loot = list(/obj/structure/closet/crate/necropolis/bubblegum)
|
||||
var/charging = 0
|
||||
medal_type = BOSS_MEDAL_BUBBLEGUM
|
||||
score_type = BUBBLEGUM_SCORE
|
||||
|
||||
achievement_type = /datum/award/achievement/boss/bubblegum_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/bubblegum_crusher
|
||||
score_achievement_type = /datum/award/score/bubblegum_score
|
||||
|
||||
deathmessage = "sinks into a pool of blood, fleeing the battle. You've won, for now... "
|
||||
death_sound = 'sound/magic/enter_blood.ogg'
|
||||
|
||||
@@ -153,6 +156,20 @@ Difficulty: Hard
|
||||
charging = 0
|
||||
Goto(target, move_to_delay, minimum_distance)
|
||||
|
||||
/**
|
||||
* Attack by override for bubblegum
|
||||
*
|
||||
* This is used to award the frenching achievement for hitting bubblegum with a tongue
|
||||
*
|
||||
* Arguments:
|
||||
* * obj/item/W the item hitting bubblegum
|
||||
* * mob/user The user of the item
|
||||
* * params, extra parameters
|
||||
*/
|
||||
/mob/living/simple_animal/hostile/megafauna/bubblegum/attackby(obj/item/W, mob/user, params)
|
||||
. = ..()
|
||||
if(istype(W, /obj/item/organ/tongue))
|
||||
user.client?.give_award(/datum/award/achievement/misc/frenching, user)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A)
|
||||
if(charging)
|
||||
|
||||
@@ -43,9 +43,10 @@ Difficulty: Very Hard
|
||||
move_to_delay = 10
|
||||
ranged = 1
|
||||
pixel_x = -32
|
||||
del_on_death = 1
|
||||
medal_type = BOSS_MEDAL_COLOSSUS
|
||||
score_type = COLOSSUS_SCORE
|
||||
del_on_death = TRUE
|
||||
achievement_type = /datum/award/achievement/boss/colossus_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/colossus_crusher
|
||||
score_achievement_type = /datum/award/score/colussus_score
|
||||
crusher_loot = list(/obj/structure/closet/crate/necropolis/colossus/crusher)
|
||||
loot = list(/obj/structure/closet/crate/necropolis/colossus)
|
||||
butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/animalhide/ashdrake = 10, /obj/item/stack/sheet/bone = 30)
|
||||
@@ -424,7 +425,7 @@ Difficulty: Very Hard
|
||||
|
||||
/obj/machinery/anomalous_crystal/honk //Strips and equips you as a clown. I apologize for nothing
|
||||
observer_desc = "This crystal strips and equips its targets as clowns."
|
||||
possible_methods = list(ACTIVATE_MOB_BUMP, ACTIVATE_SPEECH)
|
||||
possible_methods = list(ACTIVATE_TOUCH) //Because We love AOE transformations!
|
||||
activation_sound = 'sound/items/bikehorn.ogg'
|
||||
|
||||
/obj/machinery/anomalous_crystal/honk/ActivationReaction(mob/user)
|
||||
|
||||
@@ -31,6 +31,9 @@ Difficulty: Extremely Hard
|
||||
wander = FALSE
|
||||
del_on_death = TRUE
|
||||
blood_volume = BLOOD_VOLUME_NORMAL
|
||||
achievement_type = /datum/award/achievement/boss/demonic_miner_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/demonic_miner_crusher
|
||||
score_achievement_type = /datum/award/score/demonic_miner_score
|
||||
deathmessage = "falls to the ground, decaying into plasma particles."
|
||||
deathsound = "bodyfall"
|
||||
attack_action_types = list(/datum/action/innate/megafauna_attack/frost_orbs,
|
||||
|
||||
@@ -64,8 +64,9 @@ Difficulty: Medium
|
||||
guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/ashdrake = 10)
|
||||
var/swooping = NONE
|
||||
var/swoop_cooldown = 0
|
||||
medal_type = BOSS_MEDAL_DRAKE
|
||||
score_type = DRAKE_SCORE
|
||||
achievement_type = /datum/award/achievement/boss/drake_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/drake_crusher
|
||||
score_achievement_type = /datum/award/score/drake_score
|
||||
deathmessage = "collapses into a pile of bones, its flesh sloughing away."
|
||||
death_sound = 'sound/magic/demon_dies.ogg'
|
||||
var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/drake()
|
||||
@@ -398,6 +399,14 @@ Difficulty: Medium
|
||||
crusher_loot = list()
|
||||
butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/dragon/lesser/transformed //ash drake balanced around player control
|
||||
name = "transformed ash drake"
|
||||
desc = "A sentient being transformed into an ash drake"
|
||||
mob_size = MOB_SIZE_HUMAN //prevents crusher vulnerability
|
||||
move_force = MOVE_FORCE_NORMAL //stops them from destroying and unanchoring shit by walking into it
|
||||
environment_smash = ENVIRONMENT_SMASH_STRUCTURES //no we dont want sentient megafauna be able to delete the entire station in a minute flat
|
||||
damage_coeff = list(BRUTE = 0.7, BURN = 0.5, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) //200 health but not locked to standard movespeed, needs armor befitting of a dragon
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype)
|
||||
return
|
||||
|
||||
@@ -413,7 +422,8 @@ Difficulty: Medium
|
||||
if(L in hit_list || L == source)
|
||||
continue
|
||||
hit_list += L
|
||||
L.adjustFireLoss(20)
|
||||
L.adjustFireLoss(5)
|
||||
L.adjust_fire_stacks(6)
|
||||
to_chat(L, "<span class='userdanger'>You're hit by [source]'s fire breath!</span>")
|
||||
|
||||
// deals damage to mechs
|
||||
|
||||
@@ -61,8 +61,9 @@ Difficulty: Normal
|
||||
loot = list(/obj/item/hierophant_club)
|
||||
crusher_loot = list(/obj/item/hierophant_club)
|
||||
wander = FALSE
|
||||
medal_type = BOSS_MEDAL_HIEROPHANT
|
||||
score_type = HIEROPHANT_SCORE
|
||||
achievement_type = /datum/award/achievement/boss/hierophant_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/hierophant_crusher
|
||||
score_achievement_type = /datum/award/score/hierophant_score
|
||||
del_on_death = TRUE
|
||||
death_sound = 'sound/magic/repulse.ogg'
|
||||
|
||||
|
||||
@@ -42,8 +42,9 @@ SHITCODE AHEAD. BE ADVISED. Also comment extravaganza
|
||||
retreat_distance = 5
|
||||
minimum_distance = 5
|
||||
ranged_cooldown_time = 10
|
||||
medal_type = BOSS_MEDAL_LEGION
|
||||
score_type = LEGION_SCORE
|
||||
achievement_type = /datum/award/achievement/boss/legion_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/legion_crusher
|
||||
score_achievement_type = /datum/award/score/legion_score
|
||||
pixel_y = -16
|
||||
pixel_x = -32
|
||||
loot = list(/obj/item/stack/sheet/bone = 3)
|
||||
|
||||
@@ -28,24 +28,31 @@
|
||||
layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise
|
||||
flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1
|
||||
has_field_of_vision = FALSE //You are a frikkin boss
|
||||
/// Crusher loot dropped when fauna killed with a crusher
|
||||
/// Crusher loot dropped when the megafauna is killed with a crusher
|
||||
var/list/crusher_loot
|
||||
var/medal_type
|
||||
/// Score given to players when the fauna is killed
|
||||
var/score_type = BOSS_SCORE
|
||||
/// If the megafauna is actually killed (vs entering another phase)
|
||||
/// Achievement given to surrounding players when the megafauna is killed
|
||||
var/achievement_type
|
||||
/// Crusher achievement given to players when megafauna is killed
|
||||
var/crusher_achievement_type
|
||||
/// Score given to players when megafauna is killed
|
||||
var/score_achievement_type
|
||||
/// If the megafauna was actually killed (not just dying, then transforming into another type)
|
||||
var/elimination = 0
|
||||
/// Modifies attacks when at lower health
|
||||
var/anger_modifier = 0
|
||||
/// Internal tracking GPS inside fauna
|
||||
var/obj/item/gps/internal
|
||||
/// Next time fauna can use a melee attack
|
||||
/// Next time the megafauna can use a melee attack
|
||||
var/recovery_time = 0
|
||||
|
||||
var/true_spawn = TRUE // if this is a megafauna that should grant achievements, or have a gps signal
|
||||
/// If this is a megafauna that is real (has achievements, gps signal)
|
||||
var/true_spawn = TRUE
|
||||
/// Range the megafauna can move from their nest (if they have one
|
||||
var/nest_range = 10
|
||||
var/chosen_attack = 1 // chosen attack num
|
||||
/// The chosen attack by the megafauna
|
||||
var/chosen_attack = 1
|
||||
/// Attack actions, sets chosen_attack to the number in the action
|
||||
var/list/attack_action_types = list()
|
||||
/// If there is a small sprite icon for players controlling the megafauna to use
|
||||
var/small_sprite_type
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/Initialize(mapload)
|
||||
@@ -73,23 +80,22 @@
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/death(gibbed)
|
||||
/mob/living/simple_animal/hostile/megafauna/death(gibbed, list/force_grant)
|
||||
if(health > 0)
|
||||
return
|
||||
else
|
||||
var/datum/status_effect/crusher_damage/C = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
|
||||
var/crusher_kill = FALSE
|
||||
if(C && crusher_loot && C.total_damage >= maxHealth * 0.6)
|
||||
spawn_crusher_loot()
|
||||
crusher_kill = TRUE
|
||||
if(!(flags_1 & ADMIN_SPAWNED_1))
|
||||
var/tab = "megafauna_kills"
|
||||
if(crusher_kill)
|
||||
tab = "megafauna_kills_crusher"
|
||||
var/datum/status_effect/crusher_damage/crusher_dmg = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
|
||||
var/crusher_kill = FALSE
|
||||
if(crusher_dmg && crusher_loot && crusher_dmg.total_damage >= maxHealth * 0.6)
|
||||
spawn_crusher_loot()
|
||||
crusher_kill = TRUE
|
||||
if(true_spawn && !(flags_1 & ADMIN_SPAWNED_1))
|
||||
var/tab = "megafauna_kills"
|
||||
if(crusher_kill)
|
||||
tab = "megafauna_kills_crusher"
|
||||
if(!elimination) //used so the achievment only occurs for the last legion to die.
|
||||
grant_achievement(achievement_type, score_achievement_type, crusher_kill, force_grant)
|
||||
SSblackbox.record_feedback("tally", tab, 1, "[initial(name)]")
|
||||
if(!elimination) //used so the achievment only occurs for the last legion to die.
|
||||
grant_achievement(medal_type, score_type, crusher_kill)
|
||||
..()
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/proc/spawn_crusher_loot()
|
||||
loot = crusher_loot
|
||||
@@ -143,26 +149,29 @@
|
||||
if(EXPLODE_LIGHT)
|
||||
adjustBruteLoss(50)
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/proc/SetRecoveryTime(buffer_time)
|
||||
/// Sets the next time the megafauna can use a melee or ranged attack, in deciseconds
|
||||
/mob/living/simple_animal/hostile/megafauna/proc/SetRecoveryTime(buffer_time, ranged_buffer_time)
|
||||
recovery_time = world.time + buffer_time
|
||||
ranged_cooldown = max(ranged_cooldown, world.time + buffer_time) // CITADEL BANDAID FIX FOR MEGAFAUNA NOT RESPECTING RECOVERY TIME.
|
||||
ranged_cooldown = world.time + buffer_time
|
||||
if(ranged_buffer_time)
|
||||
ranged_cooldown = world.time + ranged_buffer_time
|
||||
|
||||
/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill)
|
||||
if(!medal_type || (flags_1 & ADMIN_SPAWNED_1)) //Don't award medals if the medal type isn't set
|
||||
/// Grants medals and achievements to surrounding players
|
||||
/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill, list/grant_achievement = list())
|
||||
if(!achievement_type || (flags_1 & ADMIN_SPAWNED_1) || !SSachievements.achievements_enabled) //Don't award medals if the medal type isn't set
|
||||
return FALSE
|
||||
if(!SSmedals.hub_enabled) // This allows subtypes to carry on other special rewards not tied with medals. (such as bubblegum's arena shuttle)
|
||||
return TRUE
|
||||
|
||||
for(var/mob/living/L in view(7,src))
|
||||
if(!grant_achievement.len)
|
||||
for(var/mob/living/L in view(7,src))
|
||||
grant_achievement += L
|
||||
for(var/mob/living/L in grant_achievement)
|
||||
if(L.stat || !L.client)
|
||||
continue
|
||||
var/client/C = L.client
|
||||
SSmedals.UnlockMedal("Boss [BOSS_KILL_MEDAL]", C)
|
||||
SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL]", C)
|
||||
L.client.give_award(/datum/award/achievement/boss/boss_killer, L)
|
||||
L.client.give_award(achievement_type, L)
|
||||
if(crusher_kill && istype(L.get_active_held_item(), /obj/item/kinetic_crusher))
|
||||
SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL_CRUSHER]", C)
|
||||
SSmedals.SetScore(BOSS_SCORE, C, 1)
|
||||
SSmedals.SetScore(score_type, C, 1)
|
||||
L.client.give_award(crusher_achievement_type, L)
|
||||
L.client.give_award(/datum/award/score/boss_score, L) //Score progression for bosses killed in general
|
||||
L.client.give_award(score_achievement_type, L) //Score progression for specific boss killed
|
||||
return TRUE
|
||||
|
||||
/datum/action/innate/megafauna_attack
|
||||
|
||||
@@ -48,8 +48,9 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
|
||||
health = 750
|
||||
maxHealth = 750 //""""low-ish"""" HP because it's a passive boss, and the swarm itself is the real foe
|
||||
mob_biotypes = MOB_ROBOTIC
|
||||
medal_type = BOSS_MEDAL_SWARMERS
|
||||
score_type = SWARMER_BEACON_SCORE
|
||||
achievement_type = /datum/award/achievement/boss/swarmer_beacon_kill
|
||||
crusher_achievement_type = /datum/award/achievement/boss/swarmer_beacon_crusher
|
||||
score_achievement_type = /datum/award/score/swarmer_beacon_score
|
||||
faction = list("mining", "boss", "swarmer")
|
||||
weather_immunities = list("lava","ash")
|
||||
stop_automated_movement = TRUE
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
A.GiveTarget(target)
|
||||
A.friends = friends
|
||||
A.faction = faction.Copy()
|
||||
if(!A == /mob/living/simple_animal/hostile/poison/bees/toxin)
|
||||
A.my_creator = type
|
||||
ranged_cooldown = world.time + ranged_cooldown_time
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelord/AttackingTarget()
|
||||
@@ -88,6 +90,7 @@
|
||||
density = FALSE
|
||||
del_on_death = 1
|
||||
var/swarming = FALSE
|
||||
var/my_creator = null
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/Initialize()
|
||||
. = ..()
|
||||
@@ -205,11 +208,7 @@
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/infest(mob/living/carbon/human/H)
|
||||
visible_message("<span class='warning'>[name] burrows into the flesh of [H]!</span>")
|
||||
var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L
|
||||
if(HAS_TRAIT(H, TRAIT_DWARF)) //dwarf legions aren't just fluff!
|
||||
L = new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(H.loc)
|
||||
else
|
||||
L = new(H.loc)
|
||||
var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L = check_infest_type(H)
|
||||
visible_message("<span class='warning'>[L] staggers to [L.p_their()] feet!</span>")
|
||||
H.death()
|
||||
H.adjustBruteLoss(1000)
|
||||
@@ -217,6 +216,20 @@
|
||||
H.forceMove(L)
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/check_infest_type(mob/living/carbon/human/human)
|
||||
var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L
|
||||
var/list/blacklisted_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf)
|
||||
if(HAS_TRAIT(human, TRAIT_DWARF)) //dwarf legions aren't just fluff!
|
||||
L = new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(human.loc)
|
||||
else if(my_creator)
|
||||
if(my_creator in blacklisted_types)
|
||||
L = new(human.loc)
|
||||
else
|
||||
L = new my_creator(human.loc)
|
||||
else
|
||||
L = new(human.loc)
|
||||
return L
|
||||
|
||||
//Advanced Legion is slightly tougher to kill and can raise corpses (revive other legions)
|
||||
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/advanced
|
||||
stat_attack = DEAD
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
Feedon(Food)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/slime/doUnEquip(obj/item/W)
|
||||
/mob/living/simple_animal/slime/doUnEquip(obj/item/W, silent = FALSE)
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/slime/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE)
|
||||
|
||||
@@ -178,10 +178,7 @@
|
||||
|
||||
new /obj/effect/temp_visual/monkeyify/humanify(loc)
|
||||
|
||||
transformation_timer = addtimer(CALLBACK(src, .proc/finish_humanize, tr_flags), TRANSFORMATION_DURATION, TIMER_UNIQUE)
|
||||
|
||||
/mob/living/carbon/proc/finish_humanize(tr_flags)
|
||||
transformation_timer = null
|
||||
sleep(TRANSFORMATION_DURATION) //This entire proc CANNOT be split into two
|
||||
|
||||
var/list/stored_implants = list()
|
||||
var/list/int_organs = list()
|
||||
|
||||
Reference in New Issue
Block a user