mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-11 10:43:20 +00:00
Merge pull request #3651 from SkyMarshal/airfixes
ZAS update, needs testing.
This commit is contained in:
@@ -13,68 +13,62 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
var/zone/zone_A
|
||||
var/zone/zone_B
|
||||
|
||||
var/ref_A
|
||||
var/ref_B
|
||||
|
||||
var/indirect = CONNECTION_DIRECT //If the connection is purely indirect, the zones should not join.
|
||||
|
||||
var/last_updated //The tick at which this was last updated.
|
||||
|
||||
var/no_zone_count = 0
|
||||
|
||||
|
||||
|
||||
/connection/New(turf/T,turf/O)
|
||||
. = ..()
|
||||
|
||||
A = T
|
||||
B = O
|
||||
|
||||
if(A.zone && B.zone)
|
||||
if(!A.zone.connections) A.zone.connections = list()
|
||||
if(!A.zone.connections)
|
||||
A.zone.connections = list()
|
||||
A.zone.connections += src
|
||||
zone_A = A.zone
|
||||
ref_A = "\ref[A]"
|
||||
|
||||
if(!B.zone.connections) B.zone.connections = list()
|
||||
if(!B.zone.connections)
|
||||
B.zone.connections = list()
|
||||
B.zone.connections += src
|
||||
zone_B = B.zone
|
||||
ref_B = "\ref[B]"
|
||||
|
||||
if(ref_A in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[ref_A]
|
||||
if(A in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[A]
|
||||
connections.Add(src)
|
||||
else
|
||||
air_master.turfs_with_connections[ref_A] = list(src)
|
||||
air_master.turfs_with_connections[A] = list(src)
|
||||
|
||||
if(ref_B in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[ref_B]
|
||||
if(B in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[B]
|
||||
connections.Add(src)
|
||||
else
|
||||
air_master.turfs_with_connections[ref_B] = list(src)
|
||||
air_master.turfs_with_connections[B] = list(src)
|
||||
|
||||
if(A.CanPass(null, B, 0, 0))
|
||||
|
||||
ConnectZones(A.zone, B.zone, 1)
|
||||
|
||||
if(A.HasDoor(B) || B.HasDoor(A))
|
||||
if(!A.CanPass(null, B, 1.5, 1))
|
||||
indirect = CONNECTION_INDIRECT
|
||||
|
||||
ConnectZones(A.zone, B.zone, indirect)
|
||||
|
||||
else
|
||||
ConnectZones(A.zone, B.zone)
|
||||
indirect = CONNECTION_CLOSED
|
||||
|
||||
|
||||
else
|
||||
world.log << "Attempted to create connection object for non-zone tiles: [T] ([T.x],[T.y],[T.z]) -> [O] ([O.x],[O.y],[O.z])"
|
||||
del(src)
|
||||
SoftDelete()
|
||||
|
||||
|
||||
/connection/Del()
|
||||
//remove connections from master lists.
|
||||
if(ref_B in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[ref_B]
|
||||
if(B in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[B]
|
||||
connections.Remove(src)
|
||||
|
||||
if(ref_A in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[ref_A]
|
||||
if(A in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[A]
|
||||
connections.Remove(src)
|
||||
|
||||
//Remove connection from zones.
|
||||
@@ -110,6 +104,46 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
. = ..()
|
||||
|
||||
|
||||
/connection/proc/SoftDelete()
|
||||
//remove connections from master lists.
|
||||
if(B in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[B]
|
||||
connections.Remove(src)
|
||||
|
||||
if(A in air_master.turfs_with_connections)
|
||||
var/list/connections = air_master.turfs_with_connections[A]
|
||||
connections.Remove(src)
|
||||
|
||||
//Remove connection from zones.
|
||||
if(A)
|
||||
if(A.zone && A.zone.connections)
|
||||
A.zone.connections.Remove(src)
|
||||
if(!A.zone.connections.len)
|
||||
A.zone.connections = null
|
||||
|
||||
if(istype(zone_A) && (!A || A.zone != zone_A))
|
||||
if(zone_A.connections)
|
||||
zone_A.connections.Remove(src)
|
||||
if(!zone_A.connections.len)
|
||||
zone_A.connections = null
|
||||
|
||||
if(B)
|
||||
if(B.zone && B.zone.connections)
|
||||
B.zone.connections.Remove(src)
|
||||
if(!B.zone.connections.len)
|
||||
B.zone.connections = null
|
||||
|
||||
if(istype(zone_B) && (!B || B.zone != zone_B))
|
||||
if(zone_B.connections)
|
||||
zone_B.connections.Remove(src)
|
||||
if(!zone_B.connections.len)
|
||||
zone_B.connections = null
|
||||
|
||||
//Disconnect zones while handling unusual conditions.
|
||||
// e.g. loss of a zone on a turf
|
||||
DisconnectZones(zone_A, zone_B)
|
||||
|
||||
|
||||
/connection/proc/ConnectZones(var/zone/zone_1, var/zone/zone_2, open = 0)
|
||||
|
||||
//Sanity checking
|
||||
@@ -139,6 +173,18 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
zone_2.connected_zones += zone_1
|
||||
zone_2.connected_zones[zone_1] = 1
|
||||
|
||||
if(open == CONNECTION_DIRECT)
|
||||
if(!zone_1.direct_connections)
|
||||
zone_1.direct_connections = list(src)
|
||||
else
|
||||
zone_1.direct_connections += src
|
||||
|
||||
if(!zone_2.direct_connections)
|
||||
zone_2.direct_connections = list(src)
|
||||
else
|
||||
zone_2.direct_connections += src
|
||||
|
||||
|
||||
//Handle closed connections.
|
||||
else
|
||||
|
||||
@@ -192,6 +238,15 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
if(!zone_2.connected_zones.len)
|
||||
zone_2.connected_zones = null
|
||||
|
||||
if(indirect == CONNECTION_DIRECT)
|
||||
zone_1.direct_connections -= src
|
||||
if(!zone_1.direct_connections.len)
|
||||
zone_1.direct_connections = null
|
||||
|
||||
zone_2.direct_connections -= src
|
||||
if(!zone_2.direct_connections.len)
|
||||
zone_2.direct_connections = null
|
||||
|
||||
else
|
||||
//Handle disconnection of closed zones.
|
||||
if( (zone_1 in zone_2.closed_connection_zones) || (zone_2 in zone_1.closed_connection_zones) )
|
||||
@@ -221,35 +276,23 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
|
||||
//Check sanity: existance of turfs
|
||||
if(!A || !B)
|
||||
del src
|
||||
SoftDelete()
|
||||
return
|
||||
|
||||
//Check sanity: loss of zone
|
||||
if(!A.zone || !B.zone)
|
||||
SoftDelete()
|
||||
return
|
||||
|
||||
//Check sanity: zones are different
|
||||
if(A.zone == B.zone)
|
||||
del src
|
||||
|
||||
//Check sanity: same turfs as before.
|
||||
if(ref_A != "\ref[A]" || ref_B != "\ref[B]")
|
||||
del src
|
||||
SoftDelete()
|
||||
return
|
||||
|
||||
//Handle zones changing on a turf.
|
||||
if((A.zone && A.zone != zone_A) || (B.zone && B.zone != zone_B))
|
||||
Sanitize()
|
||||
|
||||
//Manage sudden loss of a turfs zone. (e.g. a wall being built)
|
||||
if(!A.zone || !B.zone)
|
||||
no_zone_count++
|
||||
if(no_zone_count >= 5)
|
||||
//world.log << "Connection removed: [A] or [B] missing a zone."
|
||||
del src
|
||||
return 0
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
/connection/proc/CheckPassSanity()
|
||||
//Sanity check, first.
|
||||
Cleanup()
|
||||
|
||||
if(A.zone && B.zone)
|
||||
|
||||
//If no walls are blocking us...
|
||||
@@ -262,7 +305,7 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
//Make and remove connections to let air pass.
|
||||
if(indirect == CONNECTION_CLOSED)
|
||||
DisconnectZones(A.zone, B.zone)
|
||||
ConnectZones(A.zone, B.zone, 1)
|
||||
ConnectZones(A.zone, B.zone, door_pass + 1)
|
||||
|
||||
if(door_pass)
|
||||
indirect = CONNECTION_DIRECT
|
||||
@@ -277,7 +320,8 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
|
||||
//If I can no longer pass air, better delete
|
||||
else
|
||||
del src
|
||||
SoftDelete()
|
||||
return
|
||||
|
||||
/connection/proc/Sanitize()
|
||||
//If the zones change on connected turfs, update it.
|
||||
@@ -293,9 +337,6 @@ Indirect connections will not merge the two zones after they reach equilibrium.
|
||||
A = temp
|
||||
zone_B = B.zone
|
||||
zone_A = A.zone
|
||||
var/temp_ref = ref_A
|
||||
ref_A = ref_B
|
||||
ref_B = temp_ref
|
||||
return
|
||||
|
||||
//Handle removal of connections from archived zones.
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
client/proc/ZoneTick()
|
||||
set category = "Debug"
|
||||
set name = "Process Atmos"
|
||||
|
||||
var/result = air_master.Tick()
|
||||
if(result)
|
||||
src << "Sucessfully Processed."
|
||||
|
||||
else
|
||||
src << "Failed to process! ([air_master.tick_progress])"
|
||||
|
||||
|
||||
client/proc/Zone_Info(turf/T as null|turf)
|
||||
set category = "Debug"
|
||||
@@ -120,3 +131,82 @@ zone/proc/DebugDisplay(client/client)
|
||||
var/turf/zloc = pick(Z.contents)
|
||||
client << "\red Illegal air datum shared by: [zloc.loc.name]"
|
||||
|
||||
|
||||
client/proc/TestZASRebuild()
|
||||
// var/turf/turf = get_turf(mob)
|
||||
var/zone/current_zone = mob.loc:zone
|
||||
if(!current_zone)
|
||||
src << "There is no zone there!"
|
||||
return
|
||||
|
||||
var/list/current_adjacents = list()
|
||||
var/list/overlays = list()
|
||||
var/adjacent_id
|
||||
var/lowest_id
|
||||
|
||||
var/list/identical_ids = list()
|
||||
var/list/turfs = current_zone.contents.Copy()
|
||||
var/current_identifier = 1
|
||||
|
||||
for(var/turf/simulated/current in turfs)
|
||||
lowest_id = null
|
||||
current_adjacents = list()
|
||||
|
||||
for(var/direction in cardinal)
|
||||
var/turf/simulated/adjacent = get_step(current, direction)
|
||||
if(!current.ZAirPass(adjacent))
|
||||
continue
|
||||
if(turfs.Find(adjacent))
|
||||
current_adjacents += adjacent
|
||||
adjacent_id = turfs[adjacent]
|
||||
|
||||
if(adjacent_id && (!lowest_id || adjacent_id < lowest_id))
|
||||
lowest_id = adjacent_id
|
||||
|
||||
if(!lowest_id)
|
||||
lowest_id = current_identifier++
|
||||
identical_ids += lowest_id
|
||||
overlays += image('icons/misc/debug_rebuild.dmi',, "[lowest_id]")
|
||||
|
||||
for(var/turf/simulated/adjacent in current_adjacents)
|
||||
adjacent_id = turfs[adjacent]
|
||||
if(adjacent_id != lowest_id)
|
||||
if(adjacent_id)
|
||||
adjacent.overlays -= overlays[adjacent_id]
|
||||
identical_ids[adjacent_id] = lowest_id
|
||||
|
||||
turfs[adjacent] = lowest_id
|
||||
adjacent.overlays += overlays[lowest_id]
|
||||
|
||||
sleep(5)
|
||||
|
||||
if(turfs[current])
|
||||
current.overlays += overlays[turfs[current]]
|
||||
turfs[current] = lowest_id
|
||||
current.overlays += overlays[lowest_id]
|
||||
sleep(5)
|
||||
|
||||
var/list/final_arrangement = list()
|
||||
|
||||
for(var/turf/simulated/current in turfs)
|
||||
current_identifier = identical_ids[turfs[current]]
|
||||
current.overlays -= overlays[turfs[current]]
|
||||
current.overlays += overlays[current_identifier]
|
||||
sleep(5)
|
||||
|
||||
if( current_identifier > final_arrangement.len )
|
||||
final_arrangement.len = current_identifier
|
||||
final_arrangement[current_identifier] = list(current)
|
||||
|
||||
else
|
||||
final_arrangement[current_identifier] += current
|
||||
|
||||
//lazy but fast
|
||||
final_arrangement.Remove(null)
|
||||
|
||||
src << "There are [final_arrangement.len] unique segments."
|
||||
|
||||
for(var/turf/current in turfs)
|
||||
current.overlays -= overlays
|
||||
|
||||
return final_arrangement
|
||||
|
||||
@@ -8,7 +8,7 @@ What are the archived variables for?
|
||||
#define SPECIFIC_HEAT_AIR 20
|
||||
#define SPECIFIC_HEAT_CDO 30
|
||||
#define HEAT_CAPACITY_CALCULATION(oxygen,carbon_dioxide,nitrogen,toxins) \
|
||||
(carbon_dioxide*SPECIFIC_HEAT_CDO + (oxygen+nitrogen)*SPECIFIC_HEAT_AIR + toxins*SPECIFIC_HEAT_TOXIN)
|
||||
max(0, carbon_dioxide * SPECIFIC_HEAT_CDO + (oxygen + nitrogen) * SPECIFIC_HEAT_AIR + toxins * SPECIFIC_HEAT_TOXIN)
|
||||
|
||||
#define MINIMUM_HEAT_CAPACITY 0.0003
|
||||
#define QUANTIZE(variable) (round(variable,0.0001))
|
||||
|
||||
@@ -53,7 +53,6 @@ Important Procedures
|
||||
|
||||
*/
|
||||
|
||||
var/kill_air = 0
|
||||
var/tick_multiplier = 2
|
||||
|
||||
atom/proc/CanPass(atom/movable/mover, turf/target, height=1.5, air_group = 0)
|
||||
@@ -87,17 +86,24 @@ atom/proc/CanPass(atom/movable/mover, turf/target, height=1.5, air_group = 0)
|
||||
|
||||
var/datum/controller/air_system/air_master
|
||||
|
||||
/datum/controller/air_system/
|
||||
//Geoemetry lists
|
||||
/datum/controller/air_system
|
||||
//Geometry lists
|
||||
var/list/turfs_with_connections = list()
|
||||
var/list/active_hotspots = list()
|
||||
|
||||
//Special functions lists
|
||||
var/reconsidering_zones = FALSE
|
||||
var/list/tiles_to_reconsider_zones = list()
|
||||
var/list/tiles_to_reconsider_alternate
|
||||
|
||||
//Geometry updates lists
|
||||
var/updating_tiles = FALSE
|
||||
var/list/tiles_to_update = list()
|
||||
var/list/tiles_to_update_alternate
|
||||
|
||||
var/checking_connections = FALSE
|
||||
var/list/connections_to_check = list()
|
||||
var/list/connections_to_check_alternate
|
||||
|
||||
var/current_cycle = 0
|
||||
var/update_delay = 5 //How long between check should it try to process atmos again.
|
||||
@@ -106,20 +112,7 @@ var/datum/controller/air_system/air_master
|
||||
var/tick_progress = 0
|
||||
|
||||
|
||||
/* process()
|
||||
//Call this to process air movements for a cycle
|
||||
|
||||
process_rebuild_select_groups()
|
||||
//Used by process()
|
||||
//Warning: Do not call this
|
||||
|
||||
rebuild_group(datum/air_group)
|
||||
//Used by process_rebuild_select_groups()
|
||||
//Warning: Do not call this, add the group to air_master.groups_to_rebuild instead
|
||||
*/
|
||||
|
||||
|
||||
/datum/controller/air_system/proc/setup()
|
||||
/datum/controller/air_system/proc/Setup()
|
||||
//Purpose: Call this at the start to setup air groups geometry
|
||||
// (Warning: Very processor intensive but only must be done once per round)
|
||||
//Called by: Gameticker/Master controller
|
||||
@@ -147,10 +140,11 @@ var/datum/controller/air_system/air_master
|
||||
Total Simulated Turfs: [simulated_turf_count]
|
||||
Total Zones: [zones.len]
|
||||
Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_count]</font>"}
|
||||
/*
|
||||
spawn start()
|
||||
|
||||
/datum/controller/air_system/proc/start()
|
||||
// spawn Start()
|
||||
|
||||
|
||||
/datum/controller/air_system/proc/Start()
|
||||
//Purpose: This is kicked off by the master controller, and controls the processing of all atmosphere.
|
||||
//Called by: Master controller
|
||||
//Inputs: None.
|
||||
@@ -160,45 +154,60 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun
|
||||
set background = 1
|
||||
|
||||
while(1)
|
||||
if(!kill_air)
|
||||
current_cycle++
|
||||
var/success = tick() //Changed so that a runtime does not crash the ticker.
|
||||
if(!air_processing_killed)
|
||||
var/success = Tick() //Changed so that a runtime does not crash the ticker.
|
||||
if(!success) //Runtimed.
|
||||
failed_ticks++
|
||||
if(failed_ticks > 20)
|
||||
world << "<font color='red'><b>ERROR IN ATMOS TICKER. Killing air simulation!</font></b>"
|
||||
kill_air = 1
|
||||
air_processing_killed = 1
|
||||
sleep(max(5,update_delay*tick_multiplier))
|
||||
*/
|
||||
|
||||
/datum/controller/air_system/proc/tick()
|
||||
|
||||
/datum/controller/air_system/proc/Tick()
|
||||
. = 1 //Set the default return value, for runtime detection.
|
||||
|
||||
current_cycle++
|
||||
|
||||
//If there are tiles to update, do so.
|
||||
tick_progress = "update_air_properties"
|
||||
if(tiles_to_update.len) //If there are tiles to update, do so.
|
||||
if(tiles_to_update.len)
|
||||
updating_tiles = TRUE
|
||||
|
||||
for(var/turf/simulated/T in tiles_to_update)
|
||||
if(. && T && !T.update_air_properties())
|
||||
. = 0 //If a runtime occured, make sure we can sense it.
|
||||
//message_admins("ZASALERT: Unable run turf/simualted/update_air_properties()")
|
||||
//If a runtime occured, make sure we can sense it.
|
||||
. = 0
|
||||
|
||||
updating_tiles = FALSE
|
||||
|
||||
if(.)
|
||||
tiles_to_update = list()
|
||||
if(tiles_to_update_alternate)
|
||||
tiles_to_update = tiles_to_update_alternate
|
||||
tiles_to_update_alternate = null
|
||||
else
|
||||
tiles_to_update = list()
|
||||
|
||||
else if(tiles_to_update_alternate)
|
||||
tiles_to_update |= tiles_to_update_alternate
|
||||
tiles_to_update_alternate = null
|
||||
|
||||
//Check sanity on connection objects.
|
||||
if(.)
|
||||
tick_progress = "connections_to_check"
|
||||
if(connections_to_check.len)
|
||||
for(var/connection/C in connections_to_check)
|
||||
C.CheckPassSanity()
|
||||
connections_to_check = list()
|
||||
checking_connections = TRUE
|
||||
|
||||
//Ensure tiles still have zones.
|
||||
if(.)
|
||||
tick_progress = "tiles_to_reconsider_zones"
|
||||
if(tiles_to_reconsider_zones.len)
|
||||
for(var/turf/simulated/T in tiles_to_reconsider_zones)
|
||||
if(!T.zone)
|
||||
new /zone(T)
|
||||
tiles_to_reconsider_zones = list()
|
||||
for(var/connection/C in connections_to_check)
|
||||
C.Cleanup()
|
||||
|
||||
checking_connections = FALSE
|
||||
|
||||
if(connections_to_check_alternate)
|
||||
connections_to_check = connections_to_check_alternate
|
||||
connections_to_check_alternate = null
|
||||
else
|
||||
connections_to_check = list()
|
||||
|
||||
//Process zones.
|
||||
if(.)
|
||||
@@ -210,6 +219,25 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun
|
||||
Z.last_update = current_cycle
|
||||
if(. && Z && !output)
|
||||
. = 0
|
||||
|
||||
//Ensure tiles still have zones.
|
||||
if(.)
|
||||
tick_progress = "tiles_to_reconsider_zones"
|
||||
if(tiles_to_reconsider_zones.len)
|
||||
reconsidering_zones = TRUE
|
||||
|
||||
for(var/turf/simulated/T in tiles_to_reconsider_zones)
|
||||
if(!T.zone)
|
||||
new /zone(T)
|
||||
|
||||
reconsidering_zones = FALSE
|
||||
|
||||
if(tiles_to_reconsider_alternate)
|
||||
tiles_to_reconsider_zones = tiles_to_reconsider_alternate
|
||||
tiles_to_reconsider_alternate = null
|
||||
else
|
||||
tiles_to_reconsider_zones = list()
|
||||
|
||||
//Process fires.
|
||||
if(.)
|
||||
tick_progress = "active_hotspots (fire)"
|
||||
@@ -218,4 +246,54 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun
|
||||
. = 0
|
||||
|
||||
if(.)
|
||||
tick_progress = "success"
|
||||
tick_progress = "success"
|
||||
|
||||
|
||||
/datum/controller/air_system/proc/AddTurfToUpdate(turf/simulated/outdated_turf)
|
||||
var/list/tiles_to_check = list()
|
||||
|
||||
if(istype(outdated_turf))
|
||||
tiles_to_check |= outdated_turf
|
||||
|
||||
if(istype(outdated_turf, /turf))
|
||||
for(var/direction in cardinal)
|
||||
var/turf/simulated/adjacent_turf = get_step(outdated_turf, direction)
|
||||
if(istype(adjacent_turf))
|
||||
tiles_to_check |= adjacent_turf
|
||||
|
||||
if(updating_tiles)
|
||||
if(!tiles_to_update_alternate)
|
||||
tiles_to_update_alternate = tiles_to_check
|
||||
else
|
||||
tiles_to_update_alternate |= tiles_to_check
|
||||
else
|
||||
tiles_to_update |= tiles_to_check
|
||||
|
||||
|
||||
/datum/controller/air_system/proc/AddConnectionToCheck(connection/connection)
|
||||
if(checking_connections)
|
||||
if(istype(connection, /list))
|
||||
if(!connections_to_check_alternate)
|
||||
connections_to_check_alternate = connection
|
||||
|
||||
else if(!connections_to_check_alternate)
|
||||
connections_to_check_alternate = list()
|
||||
|
||||
connections_to_check_alternate |= connection
|
||||
|
||||
else
|
||||
connections_to_check |= connection
|
||||
|
||||
|
||||
/datum/controller/air_system/proc/ReconsiderTileZone(var/turf/simulated/zoneless_turf)
|
||||
if(zoneless_turf.zone)
|
||||
return
|
||||
|
||||
if(reconsidering_zones)
|
||||
if(!tiles_to_reconsider_alternate)
|
||||
tiles_to_reconsider_alternate = list()
|
||||
|
||||
tiles_to_reconsider_alternate |= zoneless_turf
|
||||
|
||||
else
|
||||
tiles_to_reconsider_zones |= zoneless_turf
|
||||
|
||||
@@ -157,14 +157,13 @@ proc/ZConnect(turf/simulated/A,turf/simulated/B)
|
||||
if(!A.zone || !B.zone) return
|
||||
if(A.zone == B.zone) return
|
||||
|
||||
if(A.CanPass(null,B,0,1))
|
||||
if(A.CanPass(null, B, 1.5, 1) && A.zone.air.compare(B.zone.air))
|
||||
return ZMerge(A.zone,B.zone)
|
||||
|
||||
//Ensure the connection isn't already made.
|
||||
if("\ref[A]" in air_master.turfs_with_connections)
|
||||
for(var/connection/C in air_master.turfs_with_connections["\ref[A]"])
|
||||
C.Cleanup()
|
||||
if(C && (C.B == B || C.A == B))
|
||||
if(A in air_master.turfs_with_connections)
|
||||
for(var/connection/C in air_master.turfs_with_connections[A])
|
||||
if(C.B == B || C.A == B)
|
||||
return
|
||||
|
||||
//Make the connection.
|
||||
|
||||
@@ -136,8 +136,8 @@
|
||||
|
||||
if(!zone && !blocks_air) //No zone, but not a wall.
|
||||
for(var/direction in DoorDirections) //Check door directions first.
|
||||
if(air_check_directions&direction)
|
||||
var/turf/simulated/T = get_step(src,direction)
|
||||
if(air_check_directions & direction)
|
||||
var/turf/simulated/T = get_step(src, direction)
|
||||
if(!istype(T))
|
||||
continue
|
||||
if(T.zone)
|
||||
@@ -145,7 +145,7 @@
|
||||
break
|
||||
if(!zone) //Still no zone
|
||||
for(var/direction in CounterDoorDirections) //Check the others second.
|
||||
if(air_check_directions&direction)
|
||||
if(air_check_directions & direction)
|
||||
var/turf/simulated/T = get_step(src,direction)
|
||||
if(!istype(T))
|
||||
continue
|
||||
@@ -158,77 +158,23 @@
|
||||
new/zone(list(src))
|
||||
|
||||
//Check pass sanity of the connections.
|
||||
if("\ref[src]" in air_master.turfs_with_connections)
|
||||
for(var/connection/C in air_master.turfs_with_connections["\ref[src]"])
|
||||
air_master.connections_to_check |= C
|
||||
if(src in air_master.turfs_with_connections)
|
||||
air_master.AddConnectionToCheck(air_master.turfs_with_connections[src])
|
||||
|
||||
if(zone && CanPass(null, src, 0, 0))
|
||||
|
||||
if(zone && !zone.rebuild)
|
||||
for(var/direction in cardinal)
|
||||
var/turf/T = get_step(src,direction)
|
||||
if(!istype(T))
|
||||
continue
|
||||
|
||||
//I can connect to air in this direction
|
||||
if(air_check_directions&direction)
|
||||
if(air_check_directions & direction && !(air_directions_archived & direction))
|
||||
|
||||
//If either block air, we must look to see if the adjacent turfs need rebuilt.
|
||||
if(!CanPass(null, T, 0, 0))
|
||||
|
||||
//Target blocks air
|
||||
if(!T.CanPass(null, T, 0, 0))
|
||||
var/turf/NT = get_step(T, direction)
|
||||
|
||||
//If that turf is in my zone still, rebuild.
|
||||
if(istype(NT,/turf/simulated) && NT in zone.contents)
|
||||
zone.rebuild = 1
|
||||
|
||||
//If that is an unsimulated tile in my zone, see if we need to rebuild or just remove.
|
||||
else if(istype(NT) && NT in zone.unsimulated_tiles)
|
||||
var/consider_rebuild = 0
|
||||
for(var/d in cardinal)
|
||||
var/turf/UT = get_step(NT,d)
|
||||
if(istype(UT, /turf/simulated) && UT.zone == zone && UT.CanPass(null, NT, 0, 0)) //If we find a neighboring tile that is in the same zone, check if we need to rebuild
|
||||
consider_rebuild = 1
|
||||
break
|
||||
if(consider_rebuild)
|
||||
zone.rebuild = 1 //Gotta check if we need to rebuild, dammit
|
||||
else
|
||||
zone.RemoveTurf(NT) //Not adjacent to anything, and unsimulated. Goodbye~
|
||||
|
||||
//To make a closed connection through closed door.
|
||||
ZConnect(T, src)
|
||||
|
||||
//If I block air.
|
||||
else if(T.zone && !T.zone.rebuild)
|
||||
var/turf/NT = get_step(src, reverse_direction(direction))
|
||||
|
||||
//If I am splitting a zone, rebuild.
|
||||
if(istype(NT,/turf/simulated) && (NT in T.zone.contents || (NT.zone && T in NT.zone.contents)))
|
||||
T.zone.rebuild = 1
|
||||
|
||||
//If NT is unsimulated, parse if I should remove it or rebuild.
|
||||
else if(istype(NT) && NT in T.zone.unsimulated_tiles)
|
||||
var/consider_rebuild = 0
|
||||
for(var/d in cardinal)
|
||||
var/turf/UT = get_step(NT,d)
|
||||
if(istype(UT, /turf/simulated) && UT.zone == T.zone && UT.CanPass(null, NT, 0, 0)) //If we find a neighboring tile that is in the same zone, check if we need to rebuild
|
||||
consider_rebuild = 1
|
||||
break
|
||||
|
||||
//Needs rebuilt.
|
||||
if(consider_rebuild)
|
||||
T.zone.rebuild = 1
|
||||
|
||||
//Not adjacent to anything, and unsimulated. Goodbye~
|
||||
else
|
||||
T.zone.RemoveTurf(NT)
|
||||
|
||||
else
|
||||
//Produce connection through open door.
|
||||
ZConnect(src,T)
|
||||
ZConnect(src,T)
|
||||
|
||||
//Something like a wall was built, changing the geometry.
|
||||
else if(air_directions_archived&direction)
|
||||
else if(!(air_check_directions & direction) && air_directions_archived & direction)
|
||||
var/turf/NT = get_step(T, direction)
|
||||
|
||||
//If the tile is in our own zone, and we cannot connect to it, better rebuild.
|
||||
@@ -290,13 +236,13 @@
|
||||
return 0
|
||||
|
||||
for(var/obj/obstacle in src)
|
||||
if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction)
|
||||
if(istype(obstacle, /obj/machinery/door) && !(obstacle:air_properties_vary_with_direction))
|
||||
continue
|
||||
if(!obstacle.CanPass(null, T, 1.5, 1))
|
||||
return 0
|
||||
|
||||
for(var/obj/obstacle in T)
|
||||
if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction)
|
||||
if(istype(obstacle, /obj/machinery/door) && !(obstacle:air_properties_vary_with_direction))
|
||||
continue
|
||||
if(!obstacle.CanPass(null, src, 1.5, 1))
|
||||
return 0
|
||||
@@ -312,13 +258,13 @@
|
||||
return 0
|
||||
|
||||
for(var/obj/obstacle in src)
|
||||
if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction)
|
||||
if(istype(obstacle, /obj/machinery/door) && !(obstacle:air_properties_vary_with_direction))
|
||||
continue
|
||||
if(!obstacle.CanPass(null, T, 0, 0))
|
||||
return 0
|
||||
|
||||
for(var/obj/obstacle in T)
|
||||
if(istype(obstacle, /obj/machinery/door) && !obstacle:air_properties_vary_with_direction)
|
||||
if(istype(obstacle, /obj/machinery/door) && !(obstacle:air_properties_vary_with_direction))
|
||||
continue
|
||||
if(!obstacle.CanPass(null, src, 0, 0))
|
||||
return 0
|
||||
|
||||
@@ -7,11 +7,15 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
var/rebuild = 0 //If 1, zone will be rebuilt on next process. Not sure if used.
|
||||
var/datum/gas_mixture/air //The air contents of the zone.
|
||||
var/list/contents //All the tiles that are contained in this zone.
|
||||
var/list/connections // /connection objects which refer to connections with other zones, e.g. through a door.
|
||||
var/list/connected_zones //Parallels connections, but lists zones to which this one is connected and the number
|
||||
//of points they're connected at.
|
||||
var/list/closed_connection_zones //Same as connected_zones, but for zones where the door or whatever is closed.
|
||||
var/list/unsimulated_tiles // Any space tiles in this list will cause air to flow out.
|
||||
|
||||
var/list/connections //connection objects which refer to connections with other zones, e.g. through a door.
|
||||
var/list/direct_connections //connections which directly connect two zones.
|
||||
|
||||
var/list/connected_zones //Parallels connections, but lists zones to which this one is connected and the number
|
||||
//of points they're connected at.
|
||||
var/list/closed_connection_zones //Same as connected_zones, but for zones where the door or whatever is closed.
|
||||
|
||||
var/last_update = 0
|
||||
var/progress = "nothing"
|
||||
|
||||
@@ -49,23 +53,22 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
zones.Add(src)
|
||||
|
||||
|
||||
//LEGACY, DO NOT USE. Use the SoftDelete proc.
|
||||
//DO NOT USE. Use the SoftDelete proc.
|
||||
/zone/Del()
|
||||
//Ensuring the zone list doesn't get clogged with null values.
|
||||
for(var/turf/simulated/T in contents)
|
||||
RemoveTurf(T)
|
||||
air_master.tiles_to_reconsider_zones += T
|
||||
air_master.ReconsiderTileZone(T)
|
||||
for(var/zone/Z in connected_zones)
|
||||
if(src in Z.connected_zones)
|
||||
Z.connected_zones.Remove(src)
|
||||
for(var/connection/C in connections)
|
||||
air_master.connections_to_check += C
|
||||
air_master.AddConnectionToCheck(connections)
|
||||
zones.Remove(src)
|
||||
air = null
|
||||
. = ..()
|
||||
|
||||
|
||||
//Handles deletion via garbage collection.
|
||||
//Handles deletion via garbage collection.
|
||||
/zone/proc/SoftDelete()
|
||||
zones.Remove(src)
|
||||
air = null
|
||||
@@ -73,7 +76,7 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
//Ensuring the zone list doesn't get clogged with null values.
|
||||
for(var/turf/simulated/T in contents)
|
||||
RemoveTurf(T)
|
||||
air_master.tiles_to_reconsider_zones += T
|
||||
air_master.ReconsiderTileZone(T)
|
||||
|
||||
//Removing zone connections and scheduling connection cleanup
|
||||
for(var/zone/Z in connected_zones)
|
||||
@@ -81,8 +84,7 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
Z.connected_zones.Remove(src)
|
||||
connected_zones = null
|
||||
|
||||
for(var/connection/C in connections)
|
||||
air_master.connections_to_check += C
|
||||
air_master.AddConnectionToCheck(connections)
|
||||
connections = null
|
||||
|
||||
return 1
|
||||
@@ -99,7 +101,9 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
contents += T
|
||||
if(air)
|
||||
air.group_multiplier++
|
||||
|
||||
T.zone = src
|
||||
|
||||
else
|
||||
if(!unsimulated_tiles)
|
||||
unsimulated_tiles = list()
|
||||
@@ -116,8 +120,13 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
contents -= T
|
||||
if(air)
|
||||
air.group_multiplier--
|
||||
|
||||
if(T.zone == src)
|
||||
T.zone = null
|
||||
|
||||
if(!contents.len)
|
||||
SoftDelete()
|
||||
|
||||
else if(unsimulated_tiles)
|
||||
unsimulated_tiles -= T
|
||||
if(!unsimulated_tiles.len)
|
||||
@@ -226,17 +235,13 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
|
||||
progress = "problem with: ZMerge(), a couple of misc procs"
|
||||
|
||||
for(var/connection/C in connections)
|
||||
//Check if the connection is valid first.
|
||||
if(!C.Cleanup())
|
||||
continue
|
||||
if(length(direct_connections))
|
||||
for(var/connection/C in direct_connections)
|
||||
|
||||
//Do merging if conditions are met. Specifically, if there's a non-door connection
|
||||
//to somewhere with space, the zones are merged regardless of equilibrium, to speed
|
||||
//up spacing in areas with double-plated windows.
|
||||
if(C && C.A.zone && C.B.zone)
|
||||
//indirect = 2 is a direct connection.
|
||||
if( C.indirect == 2 )
|
||||
//Do merging if conditions are met. Specifically, if there's a non-door connection
|
||||
//to somewhere with space, the zones are merged regardless of equilibrium, to speed
|
||||
//up spacing in areas with double-plated windows.
|
||||
if(C.A.zone && C.B.zone)
|
||||
if(C.A.zone.air.compare(C.B.zone.air) || unsimulated_tiles)
|
||||
ZMerge(C.A.zone,C.B.zone)
|
||||
|
||||
@@ -266,6 +271,7 @@ var/list/CounterDoorDirections = list(SOUTH,EAST) //Which directions doors turfs
|
||||
//If that zone has already processed, skip it.
|
||||
if(Z.last_update > last_update)
|
||||
continue
|
||||
|
||||
if(air && Z.air)
|
||||
if( abs(air.temperature - Z.air.temperature) > vsc.connection_temperature_delta )
|
||||
ShareHeat(air, Z.air, closed_connection_zones[Z])
|
||||
@@ -383,7 +389,7 @@ proc/ShareSpace(datum/gas_mixture/A, list/unsimulated_tiles, dbg_output)
|
||||
unsim_co2 *= correction_ratio
|
||||
unsim_nitrogen *= correction_ratio
|
||||
unsim_plasma *= correction_ratio
|
||||
unsim_heat_capacity = HEAT_CAPACITY_CALCULATION(unsim_oxygen,unsim_co2,unsim_nitrogen,unsim_plasma)
|
||||
unsim_heat_capacity = HEAT_CAPACITY_CALCULATION(unsim_oxygen, unsim_co2, unsim_nitrogen, unsim_plasma)
|
||||
|
||||
var
|
||||
ratio = sharing_lookup_table[6]
|
||||
@@ -402,6 +408,9 @@ proc/ShareSpace(datum/gas_mixture/A, list/unsimulated_tiles, dbg_output)
|
||||
co2_avg = (full_co2 + unsim_co2) / (size + share_size)
|
||||
plasma_avg = (full_plasma + unsim_plasma) / (size + share_size)
|
||||
|
||||
temp_avg = 0
|
||||
|
||||
if((full_heat_capacity + unsim_heat_capacity) > 0)
|
||||
temp_avg = (A.temperature * full_heat_capacity + unsim_temperature * unsim_heat_capacity) / (full_heat_capacity + unsim_heat_capacity)
|
||||
|
||||
if(sharing_lookup_table.len >= unsimulated_tiles.len) //6 or more interconnecting tiles will max at 42% of air moved per tick.
|
||||
@@ -451,72 +460,146 @@ proc/ShareHeat(datum/gas_mixture/A, datum/gas_mixture/B, connecting_tiles)
|
||||
///////////////////
|
||||
//Zone Rebuilding//
|
||||
///////////////////
|
||||
//Used for updating zone geometry when a zone is cut into two parts.
|
||||
|
||||
zone/proc/Rebuild()
|
||||
var/list/new_zone_contents = IsolateContents()
|
||||
if(new_zone_contents.len == 1)
|
||||
return
|
||||
|
||||
var/list/current_contents
|
||||
var/list/new_zones = list()
|
||||
|
||||
contents = new_zone_contents[1]
|
||||
air.group_multiplier = contents.len
|
||||
|
||||
for(var/identifier in 2 to new_zone_contents.len)
|
||||
current_contents = new_zone_contents[identifier]
|
||||
var/zone/new_zone = new (current_contents)
|
||||
new_zone.air.copy_from(air)
|
||||
new_zones += new_zone
|
||||
|
||||
for(var/connection/connection in connections)
|
||||
connection.Cleanup()
|
||||
|
||||
var/turf/simulated/adjacent
|
||||
|
||||
for(var/turf/unsimulated in unsimulated_tiles)
|
||||
for(var/direction in cardinal)
|
||||
adjacent = get_step(unsimulated, direction)
|
||||
|
||||
if(istype(adjacent) && adjacent.CanPass(null, unsimulated, 0, 0))
|
||||
for(var/zone/zone in new_zones)
|
||||
if(adjacent in zone)
|
||||
zone.AddTurf(unsimulated)
|
||||
|
||||
|
||||
//Implements a two-pass connected component labeling algorithm to determine if the zone is, in fact, split.
|
||||
|
||||
/zone/proc/IsolateContents()
|
||||
var/list/current_adjacents = list()
|
||||
var/adjacent_id
|
||||
var/lowest_id
|
||||
|
||||
var/list/identical_ids = list()
|
||||
var/list/turfs = contents.Copy()
|
||||
var/current_identifier = 1
|
||||
|
||||
for(var/turf/simulated/current in turfs)
|
||||
lowest_id = null
|
||||
current_adjacents = list()
|
||||
|
||||
for(var/direction in cardinal)
|
||||
if( !(current.air_check_directions & direction))
|
||||
continue
|
||||
var/turf/simulated/adjacent = get_step(current, direction)
|
||||
if(adjacent in turfs)
|
||||
current_adjacents += adjacent
|
||||
adjacent_id = turfs[adjacent]
|
||||
|
||||
if(adjacent_id && (!lowest_id || adjacent_id < lowest_id))
|
||||
lowest_id = adjacent_id
|
||||
|
||||
if(!lowest_id)
|
||||
lowest_id = current_identifier++
|
||||
identical_ids += lowest_id
|
||||
|
||||
for(var/turf/simulated/adjacent in current_adjacents)
|
||||
adjacent_id = turfs[adjacent]
|
||||
if(adjacent_id != lowest_id)
|
||||
if(adjacent_id)
|
||||
identical_ids[adjacent_id] = lowest_id
|
||||
turfs[adjacent] = lowest_id
|
||||
turfs[current] = lowest_id
|
||||
|
||||
var/list/final_arrangement = list()
|
||||
|
||||
for(var/turf/simulated/current in turfs)
|
||||
current_identifier = identical_ids[turfs[current]]
|
||||
|
||||
if( current_identifier > final_arrangement.len )
|
||||
final_arrangement.len = current_identifier
|
||||
final_arrangement[current_identifier] = list(current)
|
||||
|
||||
else
|
||||
final_arrangement[current_identifier] += current
|
||||
|
||||
//lazy but fast
|
||||
final_arrangement.Remove(null)
|
||||
|
||||
return final_arrangement
|
||||
|
||||
|
||||
/*
|
||||
if(!RequiresRebuild())
|
||||
return
|
||||
|
||||
//Choose a random turf and regenerate the zone from it.
|
||||
var
|
||||
turf/simulated/sample = locate() in contents
|
||||
list/new_contents
|
||||
problem = 0
|
||||
var/list/new_contents
|
||||
var/list/new_unsimulated
|
||||
|
||||
//
|
||||
var/list/turfs_to_consider = contents.Copy()
|
||||
var/list/turfs_needing_zones = list()
|
||||
|
||||
while(!sample || !sample.CanPass(null, sample, 1.5, 1))
|
||||
if(sample)
|
||||
turfs_to_consider.Remove(sample)
|
||||
sample = locate() in turfs_to_consider
|
||||
if(!sample)
|
||||
break
|
||||
var/list/zones_to_check_connections = list(src)
|
||||
|
||||
if(!istype(sample) || !sample.CanPass(null, sample, 1.5, 1)) //Not a single valid turf.
|
||||
for(var/turf/simulated/T in contents)
|
||||
air_master.tiles_to_update |= T
|
||||
if(!locate(/turf/simulated/floor) in contents)
|
||||
for(var/turf/simulated/turf in contents)
|
||||
air_master.ReconsiderTileZone(turf)
|
||||
return SoftDelete()
|
||||
|
||||
new_contents = FloodFill(sample)
|
||||
var/turfs_to_ignore = list()
|
||||
if(direct_connections)
|
||||
for(var/connection/connection in direct_connections)
|
||||
if(connection.A.zone != src)
|
||||
turfs_to_ignore += A
|
||||
else if(connection.B.zone != src)
|
||||
turfs_to_ignore += B
|
||||
|
||||
var/list/new_unsimulated = ( unsimulated_tiles ? unsimulated_tiles : list() )
|
||||
new_unsimulated = ( unsimulated_tiles ? unsimulated_tiles : list() )
|
||||
|
||||
//Now, we have allocated the new turfs into proper lists, and we can start actually rebuilding.
|
||||
|
||||
//If something isn't carried over, it will need a new zone.
|
||||
for(var/turf/T in contents)
|
||||
if(!(T in new_contents))
|
||||
RemoveTurf(T)
|
||||
turfs_needing_zones += T
|
||||
|
||||
//Handle addition of new turfs
|
||||
for(var/turf/S in new_contents)
|
||||
if(!istype(S, /turf/simulated))
|
||||
new_unsimulated |= S
|
||||
new_contents.Remove(S)
|
||||
|
||||
if(contents.len != new_contents.len)
|
||||
problem = 1
|
||||
//If something new is added, we need to deal with it seperately.
|
||||
else if(!(S in contents) && istype(S, /turf/simulated))
|
||||
if(!(S.zone in zones_to_check_connections))
|
||||
zones_to_check_connections += S.zone
|
||||
|
||||
//If something isn't carried over, there was a complication.
|
||||
for(var/turf/T in contents)
|
||||
if(!(T in new_contents))
|
||||
T.zone = null
|
||||
problem = 1
|
||||
S.zone.RemoveTurf(S)
|
||||
AddTurf(S)
|
||||
|
||||
if(problem)
|
||||
//Build some new zones for stuff that wasn't included.
|
||||
var/list/turf/simulated/rebuild_turfs = contents - new_contents
|
||||
var/list/turf/simulated/reconsider_turfs = list()
|
||||
contents = new_contents
|
||||
for(var/turf/simulated/T in rebuild_turfs)
|
||||
if(!T.zone && T.CanPass(null, T, 1.5, 1))
|
||||
var/zone/Z = new /zone(T)
|
||||
Z.air.copy_from(air)
|
||||
else
|
||||
reconsider_turfs |= T
|
||||
for(var/turf/simulated/T in reconsider_turfs)
|
||||
if(!T.zone && T.CanPass(null, T, 1.5, 1))
|
||||
var/zone/Z = new /zone(T)
|
||||
Z.air.copy_from(air)
|
||||
else if(!T in air_master.tiles_to_update)
|
||||
air_master.tiles_to_update.Add(T)
|
||||
|
||||
for(var/turf/simulated/T in contents)
|
||||
if(T.zone && T.zone != src)
|
||||
T.zone.RemoveTurf(T)
|
||||
T.zone = src
|
||||
else if(!T.zone)
|
||||
T.zone = src
|
||||
air.group_multiplier = contents.len
|
||||
//Handle the addition of new unsimulated tiles.
|
||||
unsimulated_tiles = null
|
||||
|
||||
if(new_unsimulated.len)
|
||||
@@ -528,20 +611,13 @@ zone/proc/Rebuild()
|
||||
if(istype(T) && T.zone && S.CanPass(null, T, 0, 0))
|
||||
T.zone.AddTurf(S)
|
||||
|
||||
//UNUSED
|
||||
/*
|
||||
zone/proc/connected_zones()
|
||||
//A legacy proc for getting connected zones.
|
||||
. = list()
|
||||
for(var/connection/C in connections)
|
||||
var/zone/Z
|
||||
if(C.A.zone == src)
|
||||
Z = C.B.zone
|
||||
else
|
||||
Z = C.A.zone
|
||||
//Finally, handle the orphaned turfs
|
||||
|
||||
for(var/turf/simulated/T in turfs_needing_zones)
|
||||
if(!T.zone)
|
||||
zones_to_check_connections += new /zone(T)
|
||||
|
||||
for(var/zone/zone in zones_to_check_connections)
|
||||
for(var/connection/C in zone.connections)
|
||||
C.Cleanup()*/
|
||||
|
||||
if(Z in .)
|
||||
.[Z]++
|
||||
else
|
||||
. += Z
|
||||
.[Z] = 1*/
|
||||
|
||||
Reference in New Issue
Block a user