ID:2473253
 
(See the best response by Kaiochao.)
Hello all. I am nearing the conclusion of creating a sort of super dungeon in my game which will churn through far more mobs than my game normally might. As a result, I am trying to determine the most efficient way of handling mob death/respawn, which is going to be a bit weird here. In the olden days, we just used del and hoped for the best, though I've since learned to be better and entirely rehauled that system. Currently, when mobs are killed, they are sent to an inaccessible zone (loving referred to as MobHell) where the mobs are essentially put in stasis and have their vars reset using init(). When the time comes to respawn, they're simply unfrozen and plopped back to where they need to go. I have thought about continuing to use this process, but something fundamental has changed.

What has changed:

I don't want the mobs to respawn as themselves, and this puts me in a predicament just beyond my current programming knowledge. The idea is that there are certain tiles upon which these mobs are supposed to respawn -- each mob is referenced by that turf (as its 'Tenant') in a 1:1 ratio. Simple enough, but the idea is that spawning is supposed to be varied -- the various tiles shall all have a list of mobs that can spawn on them. What I would like to somehow do is change the mob into a completely different mob as it respawns. The idea here is that I'm avoiding using del every time the mob dies. To do so might be the easy way out, but not at all efficient in an area that demands it.

The question is:

How do I type-cast one existing mob into another type of mob? When I do this:

var/mob/Soldier/M = new()
var/mob/Gunner/G = M


What is G, a Soldier, a Gunner, or Frankenstein's monster? If I had a tile that spawns either a Soldier or a Gunner, is it feasible and advisable to constantly randomly switch between the two without ever deleting the mob itself?

Now a lot of these questions I could try to answer on my own, but I'm also looking for feedback as to my method. Is it worth it to banish the defeated mob to hell, leave them in stasis elsewhere, then bring them back in due time only to transform them into a completely new entity? Would it be better at that point to just delete it manually? I am wary of the possibility of the above code causing a leak if done enough times -- if I keep creating new mobs and never manually deleting the old ones, does that blow up or will the garbage collection immediately cull M? Intuition tells me that both M and G would now point to the same location, but hopefully the type itself has changed, else the above code does absolutely nothing. If that is the case, how may I instead accomplish my stated goal?

Apologies for the many questions; the Devil's in the details, and when I'm constantly sending and fetching mobs to and from hell, I'd like to get to know him better.
In your snippet, G remains entirely a Soldier. The only difference between M and G are what methods the compiler will think are available on the variable. There's no way to change the type of an existing instance, except for turfs.

The simplest change to your current strategy that will make things work is to pool per-type as well as per-spawnpoint.

Garbage collection will automatically free the memory belonging to any atom which is not referenced anywhere (loc = null, no other atoms have a var which is set to it). 'del' is slow because it hunts and kills these references across the game's entire memory. There are a few different strategies for dealing with this. Pooling is one, 'del'ing only after giving the mob a chance to be garbage-collected is another.
Best response
In DM, the compile-time type of a variables doesn't need to match the run-time type of the value it contains.

The compile-time type of a variable tells the compiler what members you're allowing access to, from that variable, via the dot (.) operator. In other words, when you declare a variable with type T, you're telling the compiler to only allow accesses to variables and procs that the type T has. This has no effect on the actual value of the variable.

var obj/o = new/mob
// This is allowed because DM doesn't type-check values.

world << o.name
// This compiles without error because /obj has a name var.
// It runs without error because /mob also has a name var.

world << o.key
// This doesn't compile because /obj does not have a key var.
// (Can't run it if it doesn't compile.)

var mob/m = new/obj

world << m.key
// This compiles because /mob has a key var.
// However, it gives a run-time error because /obj does not have a key var.

You can't change the type of an instance at runtime.

If you're trying to change their behavior (i.e. what they do in their procs), you should instead design a type whose behavior can be changed, probably by forwarding calls to a "Behavior" datum. Give it a different Behavior and it behaves differently while still being the same instance.

If you're using different types just to identify things as a certain type of thing (if(istype(A)) do this; else do that), you could instead be using an identifying variable that can be changed at runtime such as "class" which can be set to some value representing Soldier or Gunner that can be checked without istype().

If you're using different types just to set variables (type A has one icon, type B has another) and none of the behavior is different, well, that's kind of an abuse of the inheritance system, which is meant more for differences in capabilities rather than prefabricated variations.