ID:158446
 
So, I've got these one "turret" objects. Their definition looks like this:

obj/veh/terran/turret

name = "Fletchette Turret"
desc = "Terran fletchette turrets are completely automated cannons that (yadda yadda)"

icon = 'imgObjVehTerranTurret.dmi'
icon_state = "idle"

var/currentAngle // Current angle this turret is pointing
var/const/DEGREES_TURNED_PER_TURN = 25 // This is also the angle of the maximum deviance allowed to fire per turn.

var/atom/currentTarget
var/icon/southIcon // A south facing icon.

var/const/MIN_TERRAN_TURRET_DAMAGE = 5
var/const/MAX_TERRAN_TURRET_DAMAGE = 15

density = 1
layer = 4

New(var/loc)

dir = SOUTH
currentAngle = 90

DebugMsg("Turret created and New() run. Can you see me?")

if (loc == null)
DebugMsg("[type] requires a valid location.")
del src

else if (!istype(loc,/turf/veh))
DebugMsg("[type] must be placed on a /turf/veh.")
del src

GenSouthIcon()

spawn()
sleep()
Heartbeat()

..(loc)


Oddly, this seems to happen only with this particular object in my game. Sometimes, they get created properly and New() runs. Other times, they get created and New() doesn't run.

I know they're being created because I can use a GM command to find them in the world, and other objects in the world try to interact with them, even though these turrets are invisible and have no icon.

I know that New() isn't running because otherwise I would see "Turret created and New() run. Can you see me?"

Not being created properly generates no error messages of any kind - at least where I know where to look. (Not in the chat window, not in the server profile readout.)

So, very weird thing - any ideas how I can solve this problem? I kinda need the things I ask to be created with new() to be reliably created for my game to function.

I do have a ton of processes going on, including one that runs about 100-1000 times a second before sleeping, so that's one probable conflict.
New() will be called any time you create an object. Check to make sure you don't have another definition for New(), and make sure that the object type you are creating is indeed of type /obj/veh/terran/turret. My guess is the latter.
In response to Kuraudo
Kuraudo wrote:
New() will be called any time you create an object. Check to make sure you don't have another definition for New(), and make sure that the object type you are creating is indeed of type /obj/veh/terran/turret. My guess is the latter.

I'm definitely spawning the right thing. One of the means I used to spawn them is this:

    verb/GmSummon()

set src = usr

var/tmp/mob/player/thisPlayer = usr

if (!thisPlayer.HasGMAccess())
return()

if (thisPlayer.client.statobj == null || !(istype(thisPlayer.client.statobj,/turf/veh)))
usr << "You need to target a turf to summon something here."
return()
else
var/turf/veh/summonSpot = thisPlayer.client.statobj

var/list/atom/movable/thingsToSpawn = typesof(/atom/movable)

var/atom/movable/spawnAtom = input(thisPlayer,"Select something to spawn.","Spawn Object") as anything in thingsToSpawn

var/atom/newlySpawned = new spawnAtom(summonSpot)

usr << "! [newlySpawned.type] spawned at [newlySpawned.x] [newlySpawned.y] [newlySpawned.z]"

This proc always generates the message, "! /mob/veh/terran/turret spawned at 197 210 1," and the thing spawns, but when it fails to execute New() the "Can you see me" message isn't generated and the icon doesn't show up because it's generated in the New() that never ran.

There is no other New() for this particular prototype, but I do have it going back through all the child objects' New(). These all contain "if value is null, assign this default value" code, no del src. It's sort of moot because if it was running the New() to begin with, the message would generate.

Sounds to me like I'm just really pushing the envelope of the platform and hit a bug ;]. Well, if nobody else has any ideas what could be causing this, I'll report it as such.

One thing I've noticed is that the turrets will spawn reliably after the game has been running for a little while. I suspect the issue I'm encountering is that the assets haven't been loaded into memory yet.
In response to Geldonyetich
The only thing I can think of is keep your Options & Messages window open when spawning a turret, to check for an unnoticed runtime error. I've had some very strange effects with procs crashing due to runtime errors producing "impossible" behaviour.

Also you might consider including my #1 most useful debugging snippet in your project:
// datumvars.dm
// by Hobnob
// shows a browser pop-up window listing the variables in a datum

// define VARSICON to show the icon in the popup
#define VARSICON



client
New()
verbs += /proc/Vars
..()

/proc/Vars(datum/D in world)
set category = "Debug"

var/dat = "<HEAD><TITLE>Vars for "

if(istype(D, /atom)) // if the datum is an atom
var/atom/A = D // do special handling
dat += "[A.name] : [A.type] \ref[A]</TITLE></HEAD><BODY>"

#ifdef VARSICON
if(A.icon) // if the atom has an icon, display it
dat += variable(usr, "icon", new/icon(A.icon, A.icon_state, A.dir))
#endif

else // not an atom
dat += "[D] : [D.type] \ref[D]</TITLE><HEAD><BODY>"


for(var/V in D.vars) // for each variable in the datum
dat += variable(usr, V, D.vars[V]) //get the text for that variable

dat += "</BODY>"
usr << browse(dat, "window=\ref[D]") // display the browser pop-up


// return a HTML formatted string displaying a variable

/proc/variable(user, vname, val)

var/t

if(vname == "*") // true if this variable is part of a list
t = "<FONT COLOR=#404040 SIZE=-1>* [val]" // so format smaller grey text, and just show the value
else // otherwise show the name and value without formatting
if(istext(val))
t = "<FONT>[vname] = \"[val]\"" // place quotes around text values
else
t = "<FONT>[vname] = [val]"


if(istype(val,/icon)) // if this variable is an icon, display it

#ifdef VARSICON
var/rnd = rand(1,10000) // use random number in filename to avoid conflicts
user << browse_rsc(val, "tmp\ref[val]-[rnd].png") // precache the icon image file
t+="<IMG SRC=\"tmp\ref[val]-[rnd].png\">" // and add the icon to the HTML
#endif


else if(istype(val, /datum)) // if this is a datum object
var/datum/dval = val // add a link to the object to the HTML
t+= " ([dval.type]) <A href='?src=\ref[val];Vars=1'><FONT SIZE=-2>\ref[val]</FONT></A>"

if("[val]" == "/list") // if this is a list object
t += " (length [length(val)])</FONT><BR>"

if( (vname!="vars") && (vname!="verbs") && length(val)<500) // and it's not vars or verbs, or too long

for(var/lv in val) // loop through all items in the list
t += variable(user, "*", lv) // and display them

else
t += "</FONT><BR>"

return t // return the formatted text

// topic handler for linked objects
// invoked when user clicks on an object link in the datum vars pop-up
// note all other Topic procs should call ..() first so this can be called

/datum/Topic(href, href_list)

if(href_list["Vars"]) // if this link came from the vars window

Vars(src) // invoke a new window for this object


Allows you to pull up the the vars of any atom on the map (via the right-click menu) to find out what's actually going on, which is invaluable when trying to figure out a tricky problem like this. You could pull up the vars of both a working and non-working turret and compare them directly.
In response to Hobnob
Thanks very much for the code -- I do have one variable dumper, but it's having issues with "undefined" variables, and it looks like yours has a lot more thought put into it.

This Turret failing to New() behavior is weird - no error message anywhere, including Options & Messages. Fortunately, it seems to resolve itself if I don't spawn any turrets for awhile after the game has been running.

I can think of two potential causes:

1. The assets aren't fully loaded at the start.

2. There's a ton of automation going on in my project - the processes soon become numbered in the thousands - and it's possible that there's a glut of spawned processes near the beginning that are causing it to hit some kind of spawned process upper limit.

Of course, I'm just spinning theories.
In response to Hobnob
Seeing it in action, that variable dumper is some pretty fabulous code, indeed. Thanks again.

I have done a variable dump on one of these turrets which spawned while the New() didn't trigger, and the variables reveal this (cutting out the ones that aren't BYOND-native):

type = /mob/veh/terran/turret
parent_type = /mob/veh/terran
tag =
name = "turret" // Not "Flechette Turret" as is assigned in the New()
desc = // Not assigned because New() as never triggered.
suffix =
text = "t"
icon = // It is a little strange this wasn't assigned because there is an icon before it is set.
icon_state = // Same
overlays = /list (length 0)
underlays = /list (length 0)
dir = 2
visibility = 1
luminosity = 1
opacity = 0
density = 1
layer = 4
gender = "neuter"
mouse_over_pointer =
mouse_drag_pointer =
mouse_drop_pointer = 1
mouse_drop_zone = 0
verbs = /list (length 0)
vars = /list (length 55)
contents = /list (length 0)
invisibility = 0
infra_luminosity = 0
pixel_x = 0
pixel_y = 0
mouse_opacity = 1
loc = Grassland (/turf/veh/flat) [0x1016671]
x = 202
y = 219
z = 1
sight = 0
client = // It's interesting this has a client considering this is an obj
group = /list (length 0)
ckey = // It's interesting it has a cKey and Key considering this is an /obj not a /mob
key = // Same
see_in_dark = 2
see_invisible = 0
see_infrared = 0
pixel_step_size = 0
animate_movement = 1
screen_loc =


So it's looking like it got far enough through the creation process to assign the coordinates and default name (based on type), but not to the icon before something failed and the New() was never called.

Another interesting thing is I've a .Damage() proc that should be getting called as the NPCs attack this invisible turret, and it's not being called. I know this because if it was, the the health variable (not shown here) would be set (it shows as 0) and when it reaches under 1 the object would be destroyed.

As a workaround, I'll have my summoning verb destroy whatever's summoned if the icon isn't assigned. That seems to work, but manually calling New() in this event does not. It seems the item that failed to run the New() just can't have procs run on it at all. (I guess del works on a level below this or the inability to run Del() would be a factor.)
In response to Geldonyetich
type = /mob/veh/terran/turret

Shouldn't this be /obj/veh/terran/turret?

Easy to miss unless you're looking at it with fresh eyes, which is why you're probably not noticing it.
In response to Hobnob
Hobnob wrote:
type = /mob/veh/terran/turret

Shouldn't this be /obj/veh/terran/turret?

Easy to miss unless you're looking at it with fresh eyes, which is why you're probably not noticing it.

That would explain why the key and ckey and client is showing up when there shouldn't be ones for obj.

Thanks for pointing it out - problem solved. It wasn't an evil ghost in the machine after all. Rather, I had an old mob/veh/terran/turret that was sitting around, undefined, in a code file, hiding in the middle of some otherwise commented out text, no less. It showed up in my GMSummon command, of course, and I was summoning it by accident.

So, to answer to the question, "What does it mean when New() just isn't triggered sometimes?" It means you created something without a New() in itself or parents. :P
In response to Geldonyetich
Just as an interesting suggestion, would anyone involved in this debugging process feel like typing up a Dream Makers article on any of this? I think this thread has a lot of really valuable info on how to debug a project with hidden legacy code. There are some articles on debugging now but it's a topic that could always use more coverage. Using a var dump to look at the variables, finding lost classes in your source code, etc., all belong in the debugging toolkit.

Lummox JR
In response to Lummox JR
Considering how valuable of a help Hobnob has been, I think he should have first crack at it (he certainly seems to be better at it than I). But if he's not interested, I should be able to make time to write something up.