mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-27 05:57:24 +01:00
refactors global lists.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
SUBSYSTEM_DEF(mob_hunt)
|
||||
name = "Nano-Mob Hunter GO Server"
|
||||
init_order = INIT_ORDER_NANOMOB
|
||||
priority = FIRE_PRIORITY_NANOMOB // Low priority, no need for MC_TICK_CHECK due to extremely low performance impact.
|
||||
var/max_normal_spawns = 15 //change this to adjust the number of normal spawns that can exist at one time. trapped spawns (from traitors) don't count towards this
|
||||
var/list/normal_spawns = list()
|
||||
var/max_trap_spawns = 15 //change this to adjust the number of trap spawns that can exist at one time. traps spawned beyond this point clear the oldest traps
|
||||
var/list/trap_spawns = list()
|
||||
var/list/connected_clients = list()
|
||||
var/server_status = 1 //1 is online, 0 is offline
|
||||
var/reset_cooldown = 0 //number of controller cycles before the manual_reboot proc can be used again (ignored if server is offline so you can always boot back up)
|
||||
var/obj/machinery/computer/mob_battle_terminal/red_terminal
|
||||
var/obj/machinery/computer/mob_battle_terminal/blue_terminal
|
||||
var/battle_turn = null
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/fire(resumed = FALSE)
|
||||
if(reset_cooldown) //if reset_cooldown is set (we are on cooldown, duh), reduce the remaining cooldown every cycle
|
||||
reset_cooldown--
|
||||
if(!server_status)
|
||||
return
|
||||
client_mob_update()
|
||||
if(normal_spawns.len < max_normal_spawns)
|
||||
spawn_mob()
|
||||
|
||||
//leaving this here in case admins want to use it for a random mini-event or something
|
||||
/datum/controller/subsystem/mob_hunt/proc/server_crash(recover_time = 3000)
|
||||
server_status = 0
|
||||
for(var/datum/data/pda/app/mob_hunter_game/client in connected_clients)
|
||||
client.disconnect("Server Crash")
|
||||
for(var/obj/effect/nanomob/N in trap_spawns)
|
||||
N.despawn()
|
||||
for(var/obj/effect/nanomob/N in normal_spawns)
|
||||
N.despawn()
|
||||
//just in case
|
||||
normal_spawns.Cut()
|
||||
trap_spawns.Cut()
|
||||
connected_clients.Cut()
|
||||
if(!isnum(recover_time))
|
||||
recover_time = 3000
|
||||
if(recover_time > 0) //when provided with a negative or zero valued recover_time argument, the server won't auto-restart but can be manually rebooted still
|
||||
//set a timer to automatically recover after recover_time has passed (can be manually restarted if you get impatient too)
|
||||
addtimer(CALLBACK(src, .proc/auto_recover), recover_time, TIMER_UNIQUE)
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/client_mob_update()
|
||||
var/list/ex_players = list()
|
||||
for(var/datum/data/pda/app/mob_hunter_game/client in connected_clients)
|
||||
var/mob/living/carbon/human/H = client.get_player()
|
||||
if(connected_clients[client])
|
||||
if(!H || H != connected_clients[client])
|
||||
ex_players |= connected_clients[client]
|
||||
connected_clients[client] = H
|
||||
if(ex_players.len) //to make sure we don't do this if we didn't lose any player since the last update
|
||||
for(var/obj/effect/nanomob/N in (normal_spawns + trap_spawns))
|
||||
N.conceal(ex_players)
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/auto_recover()
|
||||
if(server_status != 0)
|
||||
return
|
||||
server_status = 1
|
||||
while(normal_spawns.len < max_normal_spawns) //repopulate the server's spawns completely if we auto-recover from crash
|
||||
spawn_mob()
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/manual_reboot()
|
||||
if(server_status && reset_cooldown)
|
||||
return 0
|
||||
for(var/obj/effect/nanomob/N in trap_spawns)
|
||||
N.despawn()
|
||||
for(var/obj/effect/nanomob/N in normal_spawns)
|
||||
N.despawn()
|
||||
server_status = 1
|
||||
reset_cooldown = 25 //25 controller cycle cooldown for manual restarts
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/spawn_mob()
|
||||
var/list/nanomob_types = subtypesof(/datum/mob_hunt)
|
||||
var/datum/mob_hunt/mob_info = pick(nanomob_types)
|
||||
new mob_info()
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/register_spawn(datum/mob_hunt/mob_info)
|
||||
if(!mob_info)
|
||||
return 0
|
||||
var/obj/effect/nanomob/new_mob = new /obj/effect/nanomob(mob_info.spawn_point, mob_info)
|
||||
normal_spawns += new_mob
|
||||
new_mob.reveal()
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/register_trap(datum/mob_hunt/mob_info)
|
||||
if(!mob_info)
|
||||
return 0
|
||||
if(!mob_info.is_trap)
|
||||
return register_spawn(mob_info)
|
||||
var/obj/effect/nanomob/new_mob = new /obj/effect/nanomob(mob_info.spawn_point, mob_info)
|
||||
trap_spawns += new_mob
|
||||
new_mob.reveal()
|
||||
if(trap_spawns.len > max_trap_spawns)
|
||||
var/obj/effect/nanomob/old_trap = trap_spawns[1]
|
||||
old_trap.despawn()
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/start_check()
|
||||
if(battle_turn) //somehow we got called mid-battle, so lets just stop now
|
||||
return
|
||||
if(red_terminal && red_terminal.ready && blue_terminal && blue_terminal.ready)
|
||||
battle_turn = pick("Red", "Blue")
|
||||
red_terminal.audible_message("Battle starting!", null, 5)
|
||||
blue_terminal.audible_message("Battle starting!", null, 5)
|
||||
if(battle_turn == "Red")
|
||||
red_terminal.audible_message("Red Player's Turn!", null, 5)
|
||||
else if(battle_turn == "Blue")
|
||||
blue_terminal.audible_message("Blue Player's Turn!", null, 5)
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/launch_attack(team, raw_damage, datum/mob_type/attack_type)
|
||||
if(!team || !raw_damage)
|
||||
return
|
||||
var/obj/machinery/computer/mob_battle_terminal/target = null
|
||||
if(team == "Red")
|
||||
target = blue_terminal
|
||||
else if(team == "Blue")
|
||||
target = red_terminal
|
||||
else
|
||||
return
|
||||
target.receive_attack(raw_damage, attack_type)
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/end_battle(loser, surrender = 0)
|
||||
var/obj/machinery/computer/mob_battle_terminal/winner_terminal = null
|
||||
var/obj/machinery/computer/mob_battle_terminal/loser_terminal = null
|
||||
if(loser == "Red")
|
||||
loser_terminal = red_terminal
|
||||
winner_terminal = blue_terminal
|
||||
else if (loser == "Blue")
|
||||
loser_terminal = blue_terminal
|
||||
winner_terminal = red_terminal
|
||||
battle_turn = null
|
||||
winner_terminal.ready = 0
|
||||
loser_terminal.ready = 0
|
||||
if(surrender) //surrender doesn't give exp, to avoid people just farming exp without actually doing a battle
|
||||
winner_terminal.audible_message("Your rival surrendered!", null, 2)
|
||||
else
|
||||
var/progress_message = winner_terminal.mob_info.gain_exp()
|
||||
winner_terminal.audible_message("[winner_terminal.team] Player wins!", null, 5)
|
||||
winner_terminal.audible_message(progress_message, null, 2)
|
||||
|
||||
/datum/controller/subsystem/mob_hunt/proc/end_turn()
|
||||
red_terminal.updateUsrDialog()
|
||||
blue_terminal.updateUsrDialog()
|
||||
if(!battle_turn)
|
||||
return
|
||||
if(battle_turn == "Red")
|
||||
battle_turn = "Blue"
|
||||
blue_terminal.audible_message("Blue's turn.", null, 5)
|
||||
else if(battle_turn == "Blue")
|
||||
battle_turn = "Red"
|
||||
blue_terminal.audible_message("Red's turn.", null, 5)
|
||||
@@ -0,0 +1,262 @@
|
||||
#define CALL_SHUTTLE_REASON_LENGTH 12
|
||||
|
||||
SUBSYSTEM_DEF(shuttle)
|
||||
name = "Shuttle"
|
||||
wait = 10
|
||||
init_order = INIT_ORDER_SHUTTLE
|
||||
flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK
|
||||
runlevels = RUNLEVEL_SETUP | RUNLEVEL_GAME
|
||||
var/list/mobile = list()
|
||||
var/list/stationary = list()
|
||||
var/list/transit = list()
|
||||
|
||||
//emergency shuttle stuff
|
||||
var/obj/docking_port/mobile/emergency/emergency
|
||||
var/obj/docking_port/mobile/emergency/backup/backup_shuttle
|
||||
var/emergencyCallTime = 6000 //time taken for emergency shuttle to reach the station when called (in deciseconds)
|
||||
var/emergencyDockTime = 1800 //time taken for emergency shuttle to leave again once it has docked (in deciseconds)
|
||||
var/emergencyEscapeTime = 1200 //time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds)
|
||||
var/emergency_sec_level_time = 0 // time sec level was last raised to red or higher
|
||||
var/area/emergencyLastCallLoc
|
||||
var/emergencyNoEscape
|
||||
|
||||
//supply shuttle stuff
|
||||
var/obj/docking_port/mobile/supply/supply
|
||||
var/ordernum = 1 //order number given to next order
|
||||
var/points = 50 //number of trade-points we have
|
||||
var/points_per_decisecond = 0.005 //points gained every decisecond
|
||||
var/points_per_slip = 2 //points gained per slip returned
|
||||
var/points_per_crate = 5 //points gained per crate returned
|
||||
var/points_per_intel = 250 //points gained per intel returned
|
||||
var/points_per_plasma = 5 //points gained per plasma returned
|
||||
var/points_per_design = 25 //points gained per research design returned
|
||||
var/centcom_message = "" //Remarks from Centcom on how well you checked the last order.
|
||||
var/list/discoveredPlants = list() //Typepaths for unusual plants we've already sent CentComm, associated with their potencies
|
||||
var/list/techLevels = list()
|
||||
var/list/researchDesigns = list()
|
||||
var/list/shoppinglist = list()
|
||||
var/list/requestlist = list()
|
||||
var/list/supply_packs = list()
|
||||
var/datum/round_event/shuttle_loan/shuttle_loan
|
||||
var/sold_atoms = ""
|
||||
|
||||
/datum/controller/subsystem/shuttle/Initialize(start_timeofday)
|
||||
ordernum = rand(1,9000)
|
||||
|
||||
if(!emergency)
|
||||
WARNING("No /obj/docking_port/mobile/emergency placed on the map!")
|
||||
if(!backup_shuttle)
|
||||
WARNING("No /obj/docking_port/mobile/emergency/backup placed on the map!")
|
||||
if(!supply)
|
||||
WARNING("No /obj/docking_port/mobile/supply placed on the map!")
|
||||
|
||||
initial_load()
|
||||
|
||||
for(var/typepath in subtypesof(/datum/supply_packs))
|
||||
var/datum/supply_packs/P = new typepath()
|
||||
if(P.name == "HEADER") continue // To filter out group headers
|
||||
supply_packs["[P.type]"] = P
|
||||
initial_move()
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/shuttle/stat_entry(msg)
|
||||
..("M:[mobile.len] S:[stationary.len] T:[transit.len]")
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/initial_load()
|
||||
for(var/obj/docking_port/D in world)
|
||||
D.register()
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/shuttle/fire(resumed = FALSE)
|
||||
points += points_per_decisecond * wait
|
||||
for(var/thing in mobile)
|
||||
if(thing)
|
||||
var/obj/docking_port/mobile/P = thing
|
||||
P.check()
|
||||
continue
|
||||
CHECK_TICK
|
||||
mobile.Remove(thing)
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/getShuttle(id)
|
||||
for(var/obj/docking_port/mobile/M in mobile)
|
||||
if(M.id == id)
|
||||
return M
|
||||
WARNING("couldn't find shuttle with id: [id]")
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/getDock(id)
|
||||
for(var/obj/docking_port/stationary/S in stationary)
|
||||
if(S.id == id)
|
||||
return S
|
||||
WARNING("couldn't find dock with id: [id]")
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/requestEvac(mob/user, call_reason)
|
||||
if(!emergency)
|
||||
WARNING("requestEvac(): There is no emergency shuttle, but the shuttle was called. Using the backup shuttle instead.")
|
||||
if(!backup_shuttle)
|
||||
WARNING("requestEvac(): There is no emergency shuttle, or backup shuttle!\
|
||||
The game will be unresolvable.This is possibly a mapping error, \
|
||||
more likely a bug with the shuttle \
|
||||
manipulation system, or badminry. It is possible to manually \
|
||||
resolve this problem by loading an emergency shuttle template \
|
||||
manually, and then calling register() on the mobile docking port. \
|
||||
Good luck.")
|
||||
return
|
||||
emergency = backup_shuttle
|
||||
|
||||
if(world.time - round_start_time < config.shuttle_refuel_delay)
|
||||
to_chat(user, "The emergency shuttle is refueling. Please wait another [abs(round(((world.time - round_start_time) - config.shuttle_refuel_delay)/600))] minutes before trying again.")
|
||||
return
|
||||
|
||||
switch(emergency.mode)
|
||||
if(SHUTTLE_RECALL)
|
||||
to_chat(user, "The emergency shuttle may not be called while returning to Centcom.")
|
||||
return
|
||||
if(SHUTTLE_CALL)
|
||||
to_chat(user, "The emergency shuttle is already on its way.")
|
||||
return
|
||||
if(SHUTTLE_DOCKED)
|
||||
to_chat(user, "The emergency shuttle is already here.")
|
||||
return
|
||||
if(SHUTTLE_ESCAPE)
|
||||
to_chat(user, "The emergency shuttle is moving away to a safe distance.")
|
||||
return
|
||||
if(SHUTTLE_STRANDED)
|
||||
to_chat(user, "The emergency shuttle has been disabled by Centcom.")
|
||||
return
|
||||
|
||||
call_reason = trim(html_encode(call_reason))
|
||||
|
||||
if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH)
|
||||
to_chat(user, "You must provide a reason.")
|
||||
return
|
||||
|
||||
var/area/signal_origin = get_area(user)
|
||||
var/emergency_reason = "\nNature of emergency:\n\n[call_reason]"
|
||||
if(seclevel2num(get_security_level()) >= SEC_LEVEL_RED) // There is a serious threat we gotta move no time to give them five minutes.
|
||||
var/extra_minutes = 0
|
||||
var/priority_time = emergencyCallTime * 0.5
|
||||
if(world.time - emergency_sec_level_time < priority_time)
|
||||
extra_minutes = 5
|
||||
emergency.request(null, 0.5 + extra_minutes / (emergencyCallTime / 600), signal_origin, html_decode(emergency_reason), 1)
|
||||
else
|
||||
emergency.request(null, 1, signal_origin, html_decode(emergency_reason), 0)
|
||||
|
||||
log_game("[key_name(user)] has called the shuttle.")
|
||||
message_admins("[key_name_admin(user)] has called the shuttle.")
|
||||
|
||||
return
|
||||
|
||||
|
||||
// Called when an emergency shuttle mobile docking port is
|
||||
// destroyed, which will only happen with admin intervention
|
||||
/datum/controller/subsystem/shuttle/proc/emergencyDeregister()
|
||||
// When a new emergency shuttle is created, it will override the
|
||||
// backup shuttle.
|
||||
emergency = backup_shuttle
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/cancelEvac(mob/user)
|
||||
if(canRecall())
|
||||
emergency.cancel(get_area(user))
|
||||
log_game("[key_name(user)] has recalled the shuttle.")
|
||||
message_admins("[key_name_admin(user)] has recalled the shuttle.")
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/canRecall()
|
||||
if(emergency.mode != SHUTTLE_CALL)
|
||||
return
|
||||
if(!emergency.canRecall)
|
||||
return
|
||||
if(ticker.mode.name == "meteor")
|
||||
return
|
||||
if(seclevel2num(get_security_level()) >= SEC_LEVEL_RED)
|
||||
if(emergency.timeLeft(1) < emergencyCallTime * 0.25)
|
||||
return
|
||||
else
|
||||
if(emergency.timeLeft(1) < emergencyCallTime * 0.5)
|
||||
return
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/autoEvac()
|
||||
var/callShuttle = 1
|
||||
|
||||
for(var/thing in GLOB.shuttle_caller_list)
|
||||
if(istype(thing, /mob/living/silicon/ai))
|
||||
var/mob/living/silicon/ai/AI = thing
|
||||
if(AI.stat || !AI.client)
|
||||
continue
|
||||
else if(istype(thing, /obj/machinery/computer/communications))
|
||||
var/obj/machinery/computer/communications/C = thing
|
||||
if(C.stat & BROKEN)
|
||||
continue
|
||||
else if(istype(thing, /datum/computer_file/program/comm) || istype(thing, /obj/item/circuitboard/communications))
|
||||
continue
|
||||
|
||||
var/turf/T = get_turf(thing)
|
||||
if(T && is_station_level(T.z))
|
||||
callShuttle = 0
|
||||
break
|
||||
|
||||
if(callShuttle)
|
||||
if(emergency.mode < SHUTTLE_CALL)
|
||||
emergency.request(null, 2.5)
|
||||
log_game("There is no means of calling the shuttle anymore. Shuttle automatically called.")
|
||||
message_admins("All the communications consoles were destroyed and all AIs are inactive. Shuttle called.")
|
||||
|
||||
//try to move/request to dockHome if possible, otherwise dockAway. Mainly used for admin buttons
|
||||
/datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttleId, dockHome, dockAway, timed)
|
||||
var/obj/docking_port/mobile/M = getShuttle(shuttleId)
|
||||
if(!M)
|
||||
return 1
|
||||
var/obj/docking_port/stationary/dockedAt = M.get_docked()
|
||||
var/destination = dockHome
|
||||
if(dockedAt && dockedAt.id == dockHome)
|
||||
destination = dockAway
|
||||
if(timed)
|
||||
if(M.request(getDock(destination)))
|
||||
return 2
|
||||
else
|
||||
if(M.dock(getDock(destination)))
|
||||
return 2
|
||||
return 0 //dock successful
|
||||
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttleId, dockId, timed)
|
||||
var/obj/docking_port/mobile/M = getShuttle(shuttleId)
|
||||
var/obj/docking_port/stationary/D = getDock(dockId)
|
||||
if(!M)
|
||||
return 1
|
||||
if(timed)
|
||||
if(M.request(D))
|
||||
return 2
|
||||
else
|
||||
if(M.dock(D))
|
||||
return 2
|
||||
return 0 //dock successful
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/initial_move()
|
||||
for(var/obj/docking_port/mobile/M in mobile)
|
||||
if(!M.roundstart_move)
|
||||
continue
|
||||
M.dockRoundstart()
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/generateSupplyOrder(packId, _orderedby, _orderedbyRank, _comment, _crates)
|
||||
if(!packId)
|
||||
return
|
||||
var/datum/supply_packs/P = supply_packs["[packId]"]
|
||||
if(!P)
|
||||
return
|
||||
|
||||
var/datum/supply_order/O = new()
|
||||
O.ordernum = ordernum++
|
||||
O.object = P
|
||||
O.orderedby = _orderedby
|
||||
O.orderedbyRank = _orderedbyRank
|
||||
O.comment = _comment
|
||||
O.crates = _crates
|
||||
|
||||
requestlist += O
|
||||
|
||||
return O
|
||||
|
||||
#undef CALL_SHUTTLE_REASON_LENGTH
|
||||
@@ -0,0 +1,380 @@
|
||||
//Defines
|
||||
//Deciseconds until ticket becomes stale if unanswered. Alerts admins.
|
||||
#define ADMIN_TICKET_TIMEOUT 6000 // 10 minutes
|
||||
//Decisecions before the user is allowed to open another ticket while their existing one is open.
|
||||
#define ADMIN_TICKET_DUPLICATE_COOLDOWN 3000 // 5 minutes
|
||||
|
||||
//Status defines
|
||||
#define ADMIN_TICKET_OPEN 1
|
||||
#define ADMIN_TICKET_CLOSED 2
|
||||
#define ADMIN_TICKET_RESOLVED 3
|
||||
#define ADMIN_TICKET_STALE 4
|
||||
|
||||
SUBSYSTEM_DEF(tickets)
|
||||
name = "Tickets"
|
||||
init_order = INIT_ORDER_TICKETS
|
||||
wait = 300
|
||||
priority = FIRE_PRIORITY_TICKETS
|
||||
|
||||
flags = SS_BACKGROUND
|
||||
|
||||
var/list/allTickets
|
||||
|
||||
var/ticketCounter = 1
|
||||
|
||||
/datum/controller/subsystem/tickets/Initialize()
|
||||
LAZYINITLIST(allTickets)
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/tickets/fire()
|
||||
var/stales = checkStaleness()
|
||||
if(LAZYLEN(stales))
|
||||
var/report
|
||||
for(var/num in stales)
|
||||
report += "[num], "
|
||||
message_adminTicket("<span class='adminticket'>Tickets [report] have been open for over [ADMIN_TICKET_TIMEOUT / 600] minutes. Changing status to stale.</span>")
|
||||
|
||||
/datum/controller/subsystem/tickets/stat_entry()
|
||||
..("Tickets: [allTickets.len]")
|
||||
|
||||
/datum/controller/subsystem/tickets/proc/checkStaleness()
|
||||
var/stales = list()
|
||||
for(var/T in allTickets)
|
||||
var/datum/admin_ticket/ticket = T
|
||||
if(!(ticket.ticketState == ADMIN_TICKET_OPEN))
|
||||
continue
|
||||
if(world.time > ticket.timeUntilStale && (!ticket.lastAdminResponse || !ticket.adminAssigned))
|
||||
var/id = ticket.makeStale()
|
||||
stales += id
|
||||
return stales
|
||||
|
||||
//Return the current ticket number ready to be called off.
|
||||
/datum/controller/subsystem/tickets/proc/getTicketCounter()
|
||||
return ticketCounter
|
||||
|
||||
//Return the ticket counter and increment
|
||||
/datum/controller/subsystem/tickets/proc/getTicketCounterAndInc()
|
||||
. = ticketCounter
|
||||
ticketCounter++
|
||||
return
|
||||
|
||||
/datum/controller/subsystem/tickets/proc/resolveAllOpenTickets() // Resolve all open tickets
|
||||
for(var/i in allTickets)
|
||||
var/datum/admin_ticket/T = i
|
||||
resolveTicket(T.ticketNum)
|
||||
|
||||
//Open a new ticket and populate details then add to the list of open tickets
|
||||
/datum/controller/subsystem/tickets/proc/newTicket(client/C, passedContent, title)
|
||||
if(!C || !passedContent)
|
||||
return
|
||||
|
||||
//Check if the user has an open ticket already within the cooldown period, if so we don't create a new one and re-set the cooldown period
|
||||
var/datum/admin_ticket/existingTicket = checkForOpenTicket(C)
|
||||
if(existingTicket)
|
||||
existingTicket.setCooldownPeriod()
|
||||
to_chat(C.mob, "<span class='adminticket'>Your ticket #[existingTicket.ticketNum] remains open! Visit \"My tickets\" under the Admin Tab to view it.</span>")
|
||||
return
|
||||
|
||||
if(!title)
|
||||
title = passedContent
|
||||
|
||||
var/datum/admin_ticket/T = new(title, passedContent)
|
||||
T.clientName = C
|
||||
T.locationSent = C.mob.loc.name
|
||||
T.mobControlled = C.mob
|
||||
|
||||
//Inform the user that they have opened a ticket
|
||||
to_chat(C, "<span class='adminticket'>You have opened admin ticket number #[(SStickets.getTicketCounter() - 1)]! Please be patient and we will help you soon!</span>")
|
||||
|
||||
//Set ticket state with key N to open
|
||||
/datum/controller/subsystem/tickets/proc/openTicket(N)
|
||||
var/datum/admin_ticket/T = SStickets.allTickets[N]
|
||||
if(T.ticketState != ADMIN_TICKET_OPEN)
|
||||
T.ticketState = ADMIN_TICKET_OPEN
|
||||
return TRUE
|
||||
|
||||
//Set ticket state with key N to resolved
|
||||
/datum/controller/subsystem/tickets/proc/resolveTicket(N)
|
||||
var/datum/admin_ticket/T = SStickets.allTickets[N]
|
||||
if(T.ticketState != ADMIN_TICKET_RESOLVED)
|
||||
T.ticketState = ADMIN_TICKET_RESOLVED
|
||||
return TRUE
|
||||
|
||||
//Set ticket state with key N to closed
|
||||
/datum/controller/subsystem/tickets/proc/closeTicket(N)
|
||||
var/datum/admin_ticket/T = SStickets.allTickets[N]
|
||||
if(T.ticketState != ADMIN_TICKET_CLOSED)
|
||||
T.ticketState = ADMIN_TICKET_CLOSED
|
||||
return TRUE
|
||||
|
||||
//Check if the user already has a ticket open and within the cooldown period.
|
||||
/datum/controller/subsystem/tickets/proc/checkForOpenTicket(client/C)
|
||||
for(var/datum/admin_ticket/T in allTickets)
|
||||
if(T.clientName == C && T.ticketState == ADMIN_TICKET_OPEN && (T.ticketCooldown > world.time))
|
||||
return T
|
||||
return FALSE
|
||||
|
||||
//Check if the user has ANY ticket not resolved or closed.
|
||||
/datum/controller/subsystem/tickets/proc/checkForTicket(client/C)
|
||||
var/list/tickets = list()
|
||||
for(var/datum/admin_ticket/T in allTickets)
|
||||
if(T.clientName == C && (T.ticketState == ADMIN_TICKET_OPEN || T.ticketState == ADMIN_TICKET_STALE))
|
||||
tickets += T
|
||||
if(tickets.len)
|
||||
return tickets
|
||||
return FALSE
|
||||
|
||||
//return the client of a ticket number
|
||||
/datum/controller/subsystem/tickets/proc/returnClient(N)
|
||||
var/datum/admin_ticket/T = SStickets.allTickets[N]
|
||||
return T.clientName
|
||||
|
||||
/datum/controller/subsystem/tickets/proc/assignAdminToTicket(client/C, var/N)
|
||||
var/datum/admin_ticket/T = SStickets.allTickets[N]
|
||||
T.assignAdmin(C)
|
||||
return TRUE
|
||||
|
||||
//Single admin ticket
|
||||
|
||||
/datum/admin_ticket
|
||||
var/ticketNum // Ticket number
|
||||
var/clientName // Client which opened the ticket
|
||||
var/timeOpened // Time the ticket was opened
|
||||
var/title //The initial message with links
|
||||
var/list/content // content of the admin help
|
||||
var/lastAdminResponse // Last admin who responded
|
||||
var/lastResponseTime // When the admin last responded
|
||||
var/locationSent // Location the player was when they send the ticket
|
||||
var/mobControlled // Mob they were controlling
|
||||
var/ticketState // State of the ticket, open, closed, resolved etc
|
||||
var/timeUntilStale // When the ticket goes stale
|
||||
var/ticketCooldown // Cooldown before allowing the user to open another ticket.
|
||||
var/adminAssigned // Admin who has assigned themselves to this ticket
|
||||
|
||||
/datum/admin_ticket/New(tit, cont)
|
||||
title = tit
|
||||
content = list()
|
||||
content += cont
|
||||
timeOpened = worldtime2text()
|
||||
timeUntilStale = world.time + ADMIN_TICKET_TIMEOUT
|
||||
setCooldownPeriod()
|
||||
ticketNum = SStickets.getTicketCounterAndInc()
|
||||
ticketState = ADMIN_TICKET_OPEN
|
||||
SStickets.allTickets += src
|
||||
|
||||
//Set the cooldown period for the ticket. The time when it's created plus the defined cooldown time.
|
||||
/datum/admin_ticket/proc/setCooldownPeriod()
|
||||
ticketCooldown = world.time + ADMIN_TICKET_DUPLICATE_COOLDOWN
|
||||
|
||||
//Set the last admin who responded as the client passed as an arguement.
|
||||
/datum/admin_ticket/proc/setLastAdminResponse(client/C)
|
||||
lastAdminResponse = C
|
||||
lastResponseTime = worldtime2text()
|
||||
|
||||
//Return the ticket state as a colour coded text string.
|
||||
/datum/admin_ticket/proc/state2text()
|
||||
switch(ticketState)
|
||||
if(ADMIN_TICKET_OPEN)
|
||||
return "<font color='green'>OPEN</font>"
|
||||
if(ADMIN_TICKET_RESOLVED)
|
||||
return "<font color='blue'>RESOLVED</font>"
|
||||
if(ADMIN_TICKET_CLOSED)
|
||||
return "<font color='red'>CLOSED</font>"
|
||||
if(ADMIN_TICKET_STALE)
|
||||
return "<font color='orange'>STALE</font>"
|
||||
|
||||
//Assign the client passed to var/adminAsssigned
|
||||
/datum/admin_ticket/proc/assignAdmin(client/C)
|
||||
if(!C)
|
||||
return
|
||||
adminAssigned = C
|
||||
return TRUE
|
||||
|
||||
/datum/admin_ticket/proc/addResponse(client/C, msg)
|
||||
if(C.holder)
|
||||
setLastAdminResponse(C)
|
||||
msg = "[C]: [msg]"
|
||||
content += msg
|
||||
|
||||
/datum/admin_ticket/proc/makeStale()
|
||||
ticketState = ADMIN_TICKET_STALE
|
||||
return ticketNum
|
||||
|
||||
/*
|
||||
|
||||
UI STUFF
|
||||
|
||||
*/
|
||||
|
||||
/datum/controller/subsystem/tickets/proc/returnUI(tab = ADMIN_TICKET_OPEN)
|
||||
set name = "Open Ticket Interface"
|
||||
set category = "Tickets"
|
||||
|
||||
//dat
|
||||
var/trStyle = "border-top:2px solid; border-bottom:2px solid; padding-top: 5px; padding-bottom: 5px;"
|
||||
var/tdStyleleft = "border-top:2px solid; border-bottom:2px solid; width:150px; text-align:center;"
|
||||
var/tdStyle = "border-top:2px solid; border-bottom:2px solid;"
|
||||
var/datum/admin_ticket/ticket
|
||||
var/dat
|
||||
dat += "<head><style>.adminticket{border:2px solid}</style></head>"
|
||||
dat += "<body><h1>Admin Tickets</h1>"
|
||||
|
||||
dat +="<a href='?src=[UID()];refresh=1'>Refresh</a><br /><a href='?src=[UID()];showopen=1'>Open Tickets</a><a href='?src=[UID()];showresolved=1'>Resolved Tickets</a><a href='?src=[UID()];showclosed=1'>Closed Tickets</a>"
|
||||
if(tab == ADMIN_TICKET_OPEN)
|
||||
dat += "<h2>Open Tickets</h2>"
|
||||
dat += "<table style='width:1300px; border: 3px solid;'>"
|
||||
dat +="<tr style='[trStyle]'><th style='[tdStyleleft]'>Control</th><th style='[tdStyle]'>Ticket</th></tr>"
|
||||
if(tab == ADMIN_TICKET_OPEN)
|
||||
for(var/T in allTickets)
|
||||
ticket = T
|
||||
if(ticket.ticketState == ADMIN_TICKET_OPEN || ticket.ticketState == ADMIN_TICKET_STALE)
|
||||
dat += "<tr style='[trStyle]'><td style ='[tdStyleleft]'><a href='?src=[UID()];resolve=[ticket.ticketNum]'>Resolve</a><a href='?src=[UID()];details=[ticket.ticketNum]'>Details</a> <br /> #[ticket.ticketNum] ([ticket.timeOpened]) [ticket.ticketState == ADMIN_TICKET_STALE ? "<font color='red'><b>STALE</font>" : ""] </td><td style='[tdStyle]'><b>[ticket.title]</td></tr>"
|
||||
else
|
||||
continue
|
||||
else if(tab == ADMIN_TICKET_RESOLVED)
|
||||
dat += "<h2>Resolved Tickets</h2>"
|
||||
for(var/T in allTickets)
|
||||
ticket = T
|
||||
if(ticket.ticketState == ADMIN_TICKET_RESOLVED)
|
||||
dat += "<tr style='[trStyle]'><td style ='[tdStyleleft]'><a href='?src=[UID()];resolve=[ticket.ticketNum]'>Resolve</a><a href='?src=[UID()];details=[ticket.ticketNum]'>Details</a> <br /> #[ticket.ticketNum] ([ticket.timeOpened]) </td><td style='[tdStyle]'><b>[ticket.title]</td></tr>"
|
||||
else
|
||||
continue
|
||||
else if(tab == ADMIN_TICKET_CLOSED)
|
||||
dat += "<h2>Closed Tickets</h2>"
|
||||
for(var/T in allTickets)
|
||||
ticket = T
|
||||
if(ticket.ticketState == ADMIN_TICKET_CLOSED)
|
||||
dat += "<tr style='[trStyle]'><td style ='[tdStyleleft]'><a href='?src=[UID()];resolve=[ticket.ticketNum]'>Resolve</a><a href='?src=[UID()];details=[ticket.ticketNum]'>Details</a> <br /> #[ticket.ticketNum] ([ticket.timeOpened]) </td><td style='[tdStyle]'><b>[ticket.title]</td></tr>"
|
||||
else
|
||||
continue
|
||||
|
||||
dat += "</table></body>"
|
||||
|
||||
return dat
|
||||
|
||||
/datum/controller/subsystem/tickets/proc/showUI(mob/user, tab)
|
||||
var/dat = null
|
||||
dat = returnUI(tab)
|
||||
var/datum/browser/popup = new(user, "admintickets", "Admin Tickets", 1400, 600)
|
||||
popup.set_content(dat)
|
||||
popup.open()
|
||||
|
||||
/datum/controller/subsystem/tickets/proc/showDetailUI(mob/user, ticketID)
|
||||
var/datum/admin_ticket/T = SStickets.allTickets[ticketID]
|
||||
var/status = "[T.state2text()]"
|
||||
|
||||
var/dat = "<h1>Admin Tickets</h1>"
|
||||
|
||||
dat +="<a href='?src=[UID()];refresh=1'>Show All</a><a href='?src=[UID()];refreshdetail=[T.ticketNum]'>Refresh</a>"
|
||||
|
||||
dat += "<h2>Ticket #[T.ticketNum]</h2>"
|
||||
|
||||
dat += "<h3>[T.clientName] / [T.mobControlled] opened this ticket at [T.timeOpened] at location [T.locationSent]</h3>"
|
||||
dat += "<h4>Ticket Status: <font color='red'>[status]</font>"
|
||||
dat += "<table style='width:950px; border: 3px solid;'>"
|
||||
dat += "<tr><td>[T.title]</td></tr>"
|
||||
|
||||
if(T.content.len > 1)
|
||||
for(var/i = 2, i <= T.content.len, i++)
|
||||
dat += "<tr><td>[T.content[i]]</td></tr>"
|
||||
|
||||
dat += "</table><br /><br />"
|
||||
dat += "<a href='?src=[UID()];detailreopen=[T.ticketNum]'>Re-Open</a><a href='?src=[UID()];detailresolve=[T.ticketNum]'>Resolve</a><br /><br />"
|
||||
|
||||
if(!T.adminAssigned)
|
||||
dat += "No admin assigned to this ticket - <a href='?src=[UID()];assignadmin=[T.ticketNum]'>Take Ticket</a><br />"
|
||||
else
|
||||
dat += "[T.adminAssigned] is assigned to this Ticket. - <a href='?src=[UID()];assignadmin=[T.ticketNum]'>Take Ticket</a><br />"
|
||||
|
||||
if(T.lastAdminResponse)
|
||||
dat += "<b>Last Admin Response:</b> [T.lastAdminResponse] at [T.lastResponseTime]"
|
||||
else
|
||||
dat +="<font color='red'>No Admin Response</font>"
|
||||
|
||||
dat += "<br /><br />"
|
||||
|
||||
dat += "<a href='?src=[UID()];detailclose=[T.ticketNum]'>Close Ticket</a>"
|
||||
|
||||
var/datum/browser/popup = new(user, "adminticketsdetail", "Admin Ticket #[T.ticketNum]", 1000, 600)
|
||||
popup.set_content(dat)
|
||||
popup.open()
|
||||
|
||||
/datum/controller/subsystem/tickets/proc/userDetailUI(mob/user)
|
||||
//dat
|
||||
var/tickets = checkForTicket(user.client)
|
||||
var/dat
|
||||
dat += "<h1>Your open tickets</h1>"
|
||||
dat += "<table>"
|
||||
for(var/datum/admin_ticket/T in tickets)
|
||||
dat += "<tr><td><h2>Ticket #[T.ticketNum]</h2></td></tr>"
|
||||
for(var/i = 1, i <= T.content.len, i++)
|
||||
dat += "<tr><td>[T.content[i]]</td></tr>"
|
||||
dat += "</table>"
|
||||
|
||||
var/datum/browser/popup = new(user, "userticketsdetail", "Tickets", 1000, 600)
|
||||
popup.set_content(dat)
|
||||
popup.open()
|
||||
|
||||
/datum/controller/subsystem/tickets/Topic(href, href_list)
|
||||
|
||||
if(href_list["refresh"])
|
||||
showUI(usr)
|
||||
return
|
||||
|
||||
if(href_list["refreshdetail"])
|
||||
var/indexNum = text2num(href_list["refreshdetail"])
|
||||
showDetailUI(usr, indexNum)
|
||||
return
|
||||
|
||||
if(href_list["showopen"])
|
||||
showUI(usr, ADMIN_TICKET_OPEN)
|
||||
return
|
||||
if(href_list["showresolved"])
|
||||
showUI(usr, ADMIN_TICKET_RESOLVED)
|
||||
return
|
||||
if(href_list["showclosed"])
|
||||
showUI(usr, ADMIN_TICKET_CLOSED)
|
||||
return
|
||||
|
||||
if(href_list["details"])
|
||||
var/indexNum = text2num(href_list["details"])
|
||||
showDetailUI(usr, indexNum)
|
||||
return
|
||||
|
||||
if(href_list["resolve"])
|
||||
var/indexNum = text2num(href_list["resolve"])
|
||||
if(SStickets.resolveTicket(indexNum))
|
||||
message_adminTicket("[usr.client] / ([usr]) resolved admin ticket number [indexNum]")
|
||||
to_chat(returnClient(indexNum), "<span class='adminticket'>Your admin ticket has now been resolved.</span>")
|
||||
showUI(usr)
|
||||
|
||||
if(href_list["detailresolve"])
|
||||
var/indexNum = text2num(href_list["detailresolve"])
|
||||
if(SStickets.resolveTicket(indexNum))
|
||||
message_adminTicket("[usr.client] / ([usr]) resolved admin ticket number [indexNum]")
|
||||
to_chat(returnClient(indexNum), "<span class='adminticket'>Your admin ticket has now been resolved.</span>")
|
||||
showDetailUI(usr, indexNum)
|
||||
|
||||
if(href_list["detailclose"])
|
||||
var/indexNum = text2num(href_list["detailclose"])
|
||||
if(alert("Are you sure? This will send a negative message.",,"Yes","No") != "Yes")
|
||||
return
|
||||
if(SStickets.closeTicket(indexNum))
|
||||
message_adminTicket("[usr.client] / ([usr]) closed admin ticket number [indexNum]")
|
||||
to_chat(returnClient(indexNum), "<font color='red' size='4'><b>- AdminHelp Rejected! -</b></font>")
|
||||
to_chat(returnClient(indexNum), "<span class='boldmessage'>Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting. If you asked a question, please ensure it was clear what you were asking.</span>")
|
||||
to_chat(returnClient(indexNum), "<span class='adminticket'>Your ticket has now been closed.</span>")
|
||||
showDetailUI(usr, indexNum)
|
||||
|
||||
if(href_list["detailreopen"])
|
||||
var/indexNum = text2num(href_list["detailreopen"])
|
||||
if(SStickets.openTicket(indexNum))
|
||||
message_adminTicket("[usr.client] / ([usr]) re-opened admin ticket number [indexNum]")
|
||||
showDetailUI(usr, indexNum)
|
||||
|
||||
if(href_list["assignadmin"])
|
||||
var/indexNum = text2num(href_list["assignadmin"])
|
||||
if(SStickets.assignAdminToTicket(usr.client, indexNum))
|
||||
message_adminTicket("[usr.client] / ([usr]) has taken ticket number [indexNum]")
|
||||
to_chat(returnClient(indexNum), "<span class='adminticket'>Your ticket is being handled by [usr.client].")
|
||||
showDetailUI(usr, indexNum)
|
||||
Reference in New Issue
Block a user