When you first start programming, the problem you're trying to solve is "how do I write code that'll do ______?". Initially you're happy just to get something working, who cares what the code looks like. But when you try to develop a complete game this indifference can become a problem. As your code gets messier and messier, the project becomes harder to work on - you're more likely to write code that has bugs and they become harder to track down and fix. Eventually it'll get to the point where you're wasting a lot of time, don't feel productive, and are more likely to stop working on the project.
As a more experienced programmer, instead of asking "how do I do ______?", you should start asking "what's the best way to do ______?". In this post we'll look at what benefits you can get from using datums.
In DM, the /datum type is the most basic object type. You can create custom datum types just like you can define custom sub-types of /mob or /turf. Their purpose might seem mysterious but really it's the same. Consider how you might implement a simple combat system with different kinds of weapons:
obj
weapon
proc
attack()
sword
attack()
// sword attack
shotgun
attack()
// shotgun attack
laser
attack()
// laser attack
mob
var
obj/weapon/weapon
proc
attack()
weapon.attack()
By making the player's weapon an object, you can easily switch which attack is performed by the mob's attack() proc by just switching their weapon var. Any type of mob can use any type of weapon. To avoid using objects you could do something like this:
mob
proc
attack()
// default attack
sword_user
attack()
// sword attack
shotgun_user
attack()
// shotgun attack
laser_user
attack()
// laser attack
But you don't want the mob's attack to depend on their type, you'll probably be using mob types for something else (ex: character classes, enemy types, etc.). The mob's type and weapon can vary independently. Overriding mob procs to create different attacks doesn't allow for this.
In this example weapons are a kind of /obj because you might have players be able to pick up and drop weapons. You want them to be objects that can be placed on the map. You can use datums to create a similar effect - functionality that is controlled by an object that's separate from the mob and can easily be changed - but for things that you wouldn't need to place on the map.
For example, you could put a mob's AI in a datum:
AI
proc
decide()
plan()
act()
mob
var
AI/ai
proc
go()
ai.decide()
ai.plan()
ai.act()
The AI datum contains the logic for how mobs make decisions, plan actions, and execute those actions. You can create different child types of /AI to create different behaviors or difficulty settings. One type of /AI object can more thoroughly analyze the situation and make better decisions, while a dumber /AI object might pick actions at random.
AI
// for each type implement the three procs (decide, plan,
// and act) to create varying levels of intelligence
smart
dumb
average
// somewhere in your code:
var/mob/m = new /mob/enemy/skeleton/warlock()
if(game_difficulty == HARD)
m.ai = new /AI/smart()
else
m.ai = new /AI/average()
To do this using only procs that belong to the mob, you'd have to do something like this:
mob
proc
smart_decide()
smart_plan()
smart_act()
dumb_decide()
dumb_plan()
dumb_act()
average_decide()
average_plan()
average_act()
Then you'd need if statements or something else to decide which set of procs is called.
Putting AI routines in a datum lets you use inheritance to extend AI behavior. For example:
AI
simple
var
range = 4
decide()
// pick a target
for(var/mob/m in oview(range, owner))
if(m.dead) continue
target = m
break
bigger_range
range = 7
biggest_range
range = 10
Because the simple AI routine uses the range var, you can create child types that have different values for the range var so you can easily customize and extend the same, simple behavior. You don't have to limit yourself to just using inheritance, you can do something like this:
AI
smart
dumb
all_or_nothing
var
AI/smart/smart = new()
AI/dumb/dumb = new()
decide()
if(prob(50))
smart.decide()
else
dumb.decide()
The "all or nothing" AI type doesn't inherit from the smart or dumb AI, but it does use them to give the AI a 50% chance of doing something smart and a 50% chance of doing something dumb. If you relied on inheritance you wouldn't be able to create something like this. With inheritance you can only call the parent proc or not - you can only make use of one other AI type (the parent type). In this example you're making use of two other AI types.
Having behavior that can vary is an indication you should use objects. It's really about organization. This isn't the only way to do things, it's just easier to manage. Look through your own projects and see what might work better as a datum.
This article doesn't describe all of the ways you can use objects to organize your code. Play around with these ideas and see what else you come up with. It might seem difficult at first and cleaning up existing projects to be more organized can take a while. But, once you get in the habit, writing clean, organized code will come much more easily.
Since src in this case is a datum, oview() will likely crash and cause a runtime error.
Anyhow...
I really dislike the idea of putting AI routines in a datum.
It seems disorganized and inconvenient to have defined behaviors separated from the mob's attributes.
Programming unique behavior for unique mobs (example being a boss) would require the developer to constantly reference two separate segments of code: The boss's class definitions and the AI datum for that boss. These could also end up being in separate files, depending on how the developer likes to organize his code. Referencing different segments of code in separate files can be a pretty big headache at times, and would slow down development pretty significantly without really yielding a significant advantage in terms of robustness.
Your all or nothing example does make a good case for your implementation of AI datums, but ultimately it's not difficult to reproduce in a traditional state machine that isn't separated from the mob.
If these AI datums managed the behaviors of a group of mobs, like say a group of ants, then it would make much more sense to use them.
[EDIT]: Your solution would be a very robust one if defined behaviors were reused in multiple unrelated types of mobs. I suppose it just comes down to the designs for the mobs that will inhabit the game.