From 6edafc1a97f2cb8eb0bbfe0a56d3d8dc8013f4bc Mon Sep 17 00:00:00 2001 From: FalseIncarnate Date: Thu, 15 Sep 2016 04:23:40 -0400 Subject: [PATCH] Mob Hunt Adjustments Removes server instability, random disconnects, and server crash due to trap mobs / high player count Trap mobs are now capped just like normal spawns - If a trap mob is created that would push the total number of trap mobs over this limit, the server will remove the oldest trap mob (effectively replacing it) Changes health from a list using magic numbers to an associated list for readability (doesn't make sense to have two vars for what little uses this) Converts mob displaying to use the alternate appearance system, to allow for a per-mob control over visibility - Only will show for mobs that are holding a PDA with a game client that is connected to the game server. - Nanomobs properly are concealed from player mobs after being interacted with (capture/escape), PDA dropped, or game client disconnection - Game client will disconnect from game server when you change the active PDA app (including going to PDA main menu) and/or eject the game cartridge Also finished my thought in that one comment. It's about time I- --- code/modules/arcade/mob_hunt/game_server.dm | 47 ++++++++++-------- code/modules/arcade/mob_hunt/mob_avatar.dm | 54 +++++++++++++-------- code/modules/arcade/mob_hunt/mob_datums.dm | 47 +++++++++--------- code/modules/pda/mob_hunt_game_app.dm | 29 +++++++---- 4 files changed, 103 insertions(+), 74 deletions(-) diff --git a/code/modules/arcade/mob_hunt/game_server.dm b/code/modules/arcade/mob_hunt/game_server.dm index cbcaa400925..7058eb5f575 100644 --- a/code/modules/arcade/mob_hunt/game_server.dm +++ b/code/modules/arcade/mob_hunt/game_server.dm @@ -3,9 +3,9 @@ var/global/datum/controller/process/mob_hunt/mob_hunt_server /datum/controller/process/mob_hunt 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_instability = 0 //tracks the instability of the game server. trapped mobs and users increase instability, which causes random kicks and eventually crashing var/server_status = 1 //1 is online, 0 is offline /datum/controller/process/mob_hunt/setup() @@ -23,24 +23,12 @@ var/global/datum/controller/process/mob_hunt/mob_hunt_server /datum/controller/process/mob_hunt/doWork() if(server_status == 0) return + client_mob_update() if(normal_spawns.len < max_normal_spawns) spawn_mob() - check_stability() - if(server_instability >= 75) - if(server_instability >= 100) - server_crash() - else if(prob(server_instability - 50)) - server_crash() - if(server_instability >= 25 && connected_clients.len >= 5) - if(prob(server_instability)) - var/datum/data/pda/app/mob_hunter_game/client = pick(connected_clients) - client.disconnect("Server Communication Error") - -/datum/controller/process/mob_hunt/proc/check_stability() - server_instability = connected_clients.len + (trap_spawns.len * 5) - -/datum/controller/process/mob_hunt/proc/server_crash() +//leaving this here in case admins want to use it for a random mini-event or something +/datum/controller/process/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") @@ -52,13 +40,27 @@ var/global/datum/controller/process/mob_hunt/mob_hunt_server normal_spawns.Cut() trap_spawns.Cut() connected_clients.Cut() - //set a timer to automatically recover in 5 minutes (can be manually restarted if you get impatient too) - addtimer(src, "auto_recover", 3000, TRUE) + 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(src, "auto_recover", recover_time, TRUE) + +/datum/controller/process/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/process/mob_hunt/proc/auto_recover() if(server_status != 0) return - server_instability = 0 server_status = 1 while(normal_spawns.len < max_normal_spawns) //repopulate the server's spawns completely if we auto-recover from crash spawn_mob() @@ -68,7 +70,6 @@ var/global/datum/controller/process/mob_hunt/mob_hunt_server N.despawn() for(var/obj/effect/nanomob/N in normal_spawns) N.despawn() - check_stability() server_status = 1 /datum/controller/process/mob_hunt/proc/spawn_mob() @@ -81,6 +82,7 @@ var/global/datum/controller/process/mob_hunt/mob_hunt_server 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/process/mob_hunt/proc/register_trap(datum/mob_hunt/mob_info) @@ -90,5 +92,8 @@ var/global/datum/controller/process/mob_hunt/mob_hunt_server 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 - check_stability() + new_mob.reveal() + if(trap_spawns.len > max_trap_spawns) + var/obj/effect/nanomob/old_trap = trap_spawns[1] + old_trap.despawn() return 1 \ No newline at end of file diff --git a/code/modules/arcade/mob_hunt/mob_avatar.dm b/code/modules/arcade/mob_hunt/mob_avatar.dm index 085d2a7f6bc..cadc7d35e2f 100644 --- a/code/modules/arcade/mob_hunt/mob_avatar.dm +++ b/code/modules/arcade/mob_hunt/mob_avatar.dm @@ -2,14 +2,15 @@ /obj/effect/nanomob name = "Nano-Mob Avatar" //will be overridden by the mob datum name value when created desc = "A wild Nano-Mob appeared! Hit it with your PDA with the game open to attempt to capture it!" - invisibility = 101 //normally invisible, this gets adjusted through the game's scanning + invisibility = 101 alpha = 128 anchored = 1 //just in case density = 0 icon = 'icons/effects/mob_hunt.dmi' - //icon_state gets set in New() because it is determined by the mob_hunt datum being used + var/state_name var/datum/mob_hunt/mob_info = null var/list/clients_encountered = list() //tracks who has already interacted with us, so they can't attempt a second capture + var/image/avatar /obj/effect/nanomob/New(loc, datum/mob_hunt/new_info) ..() @@ -28,21 +29,20 @@ name = mob_info.mob_name desc = "A wild [name] (level [mob_info.level]) appeared! Hit it with your PDA with the game open to attempt to capture it!" if(mob_info.is_shiny) - icon_state = mob_info.icon_state_shiny + state_name = mob_info.icon_state_shiny else - icon_state = mob_info.icon_state_normal + state_name = mob_info.icon_state_normal + avatar = image(icon, src, state_name) + avatar.override = 1 + add_alt_appearance("nanomob_avatar", avatar) /obj/effect/nanomob/attackby(obj/item/O, mob/user) - if(invisibility) //no catching mobs you can't normally see! - return if(istype(O, /obj/item/device/pda)) var/obj/item/device/pda/P = O attempt_capture(P, -20) //attempting a melee capture reduces the mob's effective run_chance by 20% to balance the risk of triggering a trap mob return 1 /obj/effect/nanomob/hitby(obj/item/O) - if(invisibility) //no catching mobs you can't normally see! - return if(istype(O, /obj/item/device/pda)) var/obj/item/device/pda/P = O attempt_capture(P) //attempting a ranged capture does not affect the mob's effective run_chance but does prevent you from being shocked by a trap mob @@ -63,7 +63,7 @@ return if(istype(P.loc, /mob/living/carbon)) var/mob/living/carbon/C = P.loc - //Strike them down with a lightning bolt to complete the illusion (copied from the surge reagent overdose, probably could + //Strike them down with a lightning bolt to complete the illusion (copied from the surge reagent overdose, probably could make this a general-use proc in the future) playsound(get_turf(C), 'sound/effects/eleczap.ogg', 75, 1) var/icon/I=new('icons/obj/zap.dmi',"lightningend") I.Turn(-135) @@ -75,36 +75,50 @@ C.electrocute_act(20, P, 1) //same damage as a revenant's overload ability, except subject to gloves/species shock resistance (for human mobs at least) return - var/obj/item/weapon/cartridge/cartridge = P.cartridge - var/cart_ref = "\ref[cartridge]" - if(cart_ref in clients_encountered) //we've already dealt with you, go away! + if(client in clients_encountered) //we've already dealt with you, go away! return else //deal with the new hunter by either running away or getting caught - clients_encountered += cart_ref + clients_encountered += client var/message = "[bicon(P)] " var/effective_run_chance = mob_info.run_chance + total_catch_mod if((effective_run_chance > 0) && prob(effective_run_chance)) message += "Capture failed! [name] escaped [P.owner ? "from [P.owner]" : "from this hunter"]!" + conceal(client) else if(client.register_capture(mob_info, 1)) message += "Capture success! [P.owner ? P.owner : "This hunter"] captured [name]!" + conceal(client) else message += "Capture error! Try again." - clients_encountered -= cart_ref //if the capture registration failed somehow, let them have another chance with this mob + clients_encountered -= client //if the capture registration failed somehow, let them have another chance with this mob P.audible_message(message, null, 4) /obj/effect/nanomob/proc/despawn() if(mob_hunt_server) if(mob_info.is_trap) mob_hunt_server.trap_spawns -= src - mob_hunt_server.check_stability() else mob_hunt_server.normal_spawns -= src qdel(src) /obj/effect/nanomob/proc/reveal() - if(invisibility == 101) - invisibility = 0 - spawn(30) - if(src) - invisibility = 101 + if(!mob_hunt_server) + return + var/list/show_to = list() + for(var/A in mob_hunt_server.connected_clients) + if((A in clients_encountered) || !mob_hunt_server.connected_clients[A]) + continue + show_to |= mob_hunt_server.connected_clients[A] + display_alt_appearance("nanomob_avatar", show_to) + +/obj/effect/nanomob/proc/conceal(list/hide_from) + if(!mob_hunt_server) + return + var/list/hiding_from = list() + if(hide_from) + hiding_from = hide_from + else + for(var/A in mob_hunt_server.connected_clients) + if((A in clients_encountered) && mob_hunt_server.connected_clients[A]) + hiding_from |= mob_hunt_server.connected_clients[A] + hide_alt_appearance("nanomob_avatar", hiding_from) diff --git a/code/modules/arcade/mob_hunt/mob_datums.dm b/code/modules/arcade/mob_hunt/mob_datums.dm index 20dd869bf28..e0930a0ee67 100644 --- a/code/modules/arcade/mob_hunt/mob_datums.dm +++ b/code/modules/arcade/mob_hunt/mob_datums.dm @@ -34,7 +34,7 @@ var/base_health = 5 //base health of the mob for battling (effectively max health at level 0) var/attack_multiplier = 1 //how much additional damage per level the mob deals (level * attack_multiplier) var/health_multiplier = 1 //how much additional health per level the mob gets (level * health_multiplier) for calculating max health - var/health = list(0, 0) //health[1] is the mob's current health, health[2] is the mob's max health (calculated in New()) + var/health = list("current" = 0, "max" = 0) //SPAWN PREFERENCES AND VARIABLES //A note on mob spawn preferences: The mob types also have preferences, which are handled prior to per-mob preferences, so ultimately you use a combined set of preferences @@ -61,7 +61,7 @@ level = rand(min_level, max_level) if(prob(1) && prob(1)) is_shiny = 1 - health[2] = base_health + (level * health_multiplier) + health["max"] = base_health + (level * health_multiplier) if(primary_type) primary_type = new primary_type() if(secondary_type) @@ -152,28 +152,27 @@ var/list/possible_turfs = list() //setup, sets all turfs in spawn_area to weight 1 for(var/turf/T in spawn_area) - if(T.z > 1) //mobs will only consider station-level turfs for spawning. Largely here so we won't have to worry about mapping errors or mobs on the derelict solars + if(!is_station_level(T.z)) //mobs will only consider station-level turfs for spawning. Largely here so we won't have to worry about mapping errors or mobs on the derelict solars continue possible_turfs[T] = 1 - //primary type preferences - if(primary_type) - for(var/turf/T in primary_type.turf_whitelist) + //primary type preferences + if(primary_type) + if(is_type_in_list(T, primary_type.turf_whitelist)) + possible_turfs[T] += 4 + if(is_type_in_list(T, primary_type.turf_blacklist)) + possible_turfs[T] -= 2 + //secondary type preferences + if(secondary_type) + if(is_type_in_list(T, secondary_type.turf_whitelist)) + possible_turfs[T] += 4 + if(is_type_in_list(T, secondary_type.turf_blacklist)) + possible_turfs[T] -= 2 + //mob preferences + if(is_type_in_list(T, turf_whitelist)) possible_turfs[T] += 4 - for(var/turf/T in primary_type.turf_blacklist) + if(is_type_in_list(T, turf_blacklist)) possible_turfs[T] -= 2 - //secondary type preferences - if(secondary_type) - for(var/turf/T in secondary_type.turf_whitelist) - possible_turfs[T] += 4 - for(var/turf/T in secondary_type.turf_blacklist) - possible_turfs[T] -= 2 - //mob preferences - for(var/turf/T in turf_whitelist) - possible_turfs[T] += 4 - for(var/turf/T in turf_blacklist) - possible_turfs[T] -= 2 - //weight check, remove negative or zero weight turfs from the list, then return the list - for(var/T in possible_turfs) + //weight check, remove negative or zero weight turfs from the list, then return the list if(possible_turfs[T] < 1) possible_turfs -= T return possible_turfs @@ -210,9 +209,9 @@ var/message = "" var/multiplier = calc_dam_multiplier(attack_type) var/total_damage = raw_damage * multiplier - if(!health[1]) //it's already downed, quit hitting it + if(!health["current"]) //it's already downed, quit hitting it return null - health[1] = max(health[1] - total_damage, 0) + health["current"] = max(health["current"] - total_damage, 0) switch(multiplier) if(0) message += "The attack is completely ineffective! " @@ -224,7 +223,7 @@ message += "It's super effective! " if(4) message += "It's ultra effective! " - if(!health[1]) + if(!health["current"]) message += "[nickname ? nickname : mob_name] is downed!" return message @@ -236,7 +235,7 @@ message += "[nickname ? nickname : mob_name] can't get any stronger right now!" else - health[2] = base_health + (level * health_multiplier) + health["max"] = base_health + (level * health_multiplier) message += "[nickname ? nickname : mob_name] has reached level [level]!" /datum/mob_hunt/proc/get_type1() diff --git a/code/modules/pda/mob_hunt_game_app.dm b/code/modules/pda/mob_hunt_game_app.dm index 512f320a32b..f41d046ae5c 100644 --- a/code/modules/pda/mob_hunt_game_app.dm +++ b/code/modules/pda/mob_hunt_game_app.dm @@ -17,6 +17,7 @@ var/hacked = 0 //if set, this cartridge is able to spawn trap mobs from its collection (set via emag_act on the cartridge) var/catch_mod = 0 //used to adjust the likelihood of a mob running from this client, a negative value means it is less likely to run (support for temporary bonuses) var/wild_captures = 0 //used to track the total number of mobs captured from the wild (does not count card mobs) by this client + var/scan_range = 3 //maximum distance (in tiles) from which the client can reveal nearby mobs /datum/data/pda/app/mob_hunter_game/start() ..() @@ -24,18 +25,20 @@ /datum/data/pda/app/mob_hunter_game/stop() ..() + disconnect("Program Terminated") processing_objects.Remove(pda) /datum/data/pda/app/mob_hunter_game/proc/scan_nearby() if(!mob_hunt_server || !connected) return - for(var/turf/T in range(3, get_turf(pda))) - for(var/obj/O in T.contents) - if(!istype(O, /obj/effect/nanomob)) - continue - var/obj/effect/nanomob/N = O - //reveal the mob - N.reveal() + for(var/turf/T in range(scan_range, get_turf(pda))) + for(var/obj/effect/nanomob/N in T.contents) + if(src in N.clients_encountered) + //hide the mob + N.conceal() + else + //reveal the mob + N.reveal() /datum/data/pda/app/mob_hunter_game/proc/reconnect() if(!mob_hunt_server || !mob_hunt_server.server_status || connected) @@ -47,10 +50,20 @@ pda.audible_message("[bicon(pda)] Connection established. Capture all of the mobs, [pda.owner ? pda.owner : "hunter"]!", null, 2) return 1 +/datum/data/pda/app/mob_hunter_game/proc/get_player() + if(!pda) + return null + if(ishuman(pda.loc)) + var/mob/living/carbon/human/H = pda.loc + return H + return null + /datum/data/pda/app/mob_hunter_game/proc/disconnect(reason = null) if(!mob_hunt_server || !connected) return mob_hunt_server.connected_clients -= src + for(var/obj/effect/nanomob/N in (mob_hunt_server.normal_spawns + mob_hunt_server.trap_spawns)) + N.conceal(list(get_player())) connected = 0 //show a disconnect message if we were disconnected involuntarily (reason argument provided) if(pda && reason) @@ -105,8 +118,6 @@ if(mob_info.is_shiny) entry["sprite"] = "[mob_info.icon_state_shiny].png" data["entry"] = entry - for(var/a in entry) - log_debug("[a] = [entry[a]]") /datum/data/pda/app/mob_hunter_game/proc/assign_nickname() if(!my_collection.len)