Let's go point by point.
> No, I disagree, because the right behavior is not determined by a single object but by a combination of factors
Sure, and you can ask one or more of the objects involved about all of those factors!
> The check that the "enemy" is even a valid target (who decides that? The player? What if certain enemies are immune to slashing attacks? The sword then? What about the enemy making the decision? What if it depends on the armor the enemy is wearing, does the armor decide?)
Answer: All of them! Let's walk through it step-by-step:
- The player wants to attack the entity it's pointing at: `aPlayer attackEntityCurrentlyPointedAtIn: world.`
- In order to do this, the player asks the world what it's pointing at first: `entity: world entityPointedAtBy: self.` If nothing is returned, then the player would simply play the shoot animation of the wielded weapon.
- If an entity is returned, the player asks the enemy to be attacked by itself: `entity getAttackedBy: self`.
- The enemy checks whether it's the right kind of entity first (attacking a prop might do nothing, for instance). Afterwards, it asks the source entity about the various factors you talked about (what kind of attack the source's currently wielded weapon would produce, what the armor is, etc.) and responds whether it could be attacked and how much damage was produced. Maybe the enemy could even deal damage back! `source dealDamage: 123 Source: damageSources reflection.`
> The calculation of the position of where the sword impacted the enemy (who calculates that? it depends on the animation calculation results, the size and shape of the sword and of the enemy, etc.)
We already would need the animation context in order to make sense of the animation that would be produced by the attack at this point, so we can just thread it through our messages and ask where on the target entity the strike would happen. Because we would be asking the wielded weapon about the details of what kind of animation it would produce, it can add in any details and modifiers you'd like.
> The sound that plays depends on the type of equipment the enemy has (leather armor vs steel armor) and on the weapon of the player (a sword or a hammer would have different sounds).
> The actual playback of the sound depends on all of the decisions above (the animation, the collision, the materials involved in the collision).
Let's just ask the attack source about what kind of material it is, and then what it would produce if it hit us, and then ask the sound to play:
weaponMaterial: source wieldedWeapon material.
"Assuming you need this level of detail"
ownMaterialAtStrikePoint: strikeSurface material.
weaponMaterial soundForStrikingAgainst: ownMaterialAtStrikePoint
; play: audioContext.
> At no point, in any of this, are there messages between these objects.
I think I was able to prove this wrong. ;)
> The entities that have to make these calculations depend on multiple data sources, none of which belong to them, that is, the behavior is not associated with the data.
This isn't how the real world works though. Every action has a fundamental source and a target. Being able to model how different things interact in our system in such a natural manner like message passing can make everything so much clearer.
> An animation doesn't render itself (rendering requires a rendering context, mesh data, texture data, shaders, etc., who owns them?). A sound doesn't play itself. Collisions don't calculate themselves.
But they do! They are the best at rendering, playing and calculating themselves, because they're the information expert: they hold all the details. The code that sends the `play:` message need not know anything about whether the sound is a waveform or Opus; it need not know whether the sound object is actually a `pitchChange` object that modifies the original sound effect. All it needs to know is that the object can be `play:`ed.
> Inert objects don't inherit behavior because they have no behavior, systems do.
A system is simply a hierarchy of objects, as I detailed above, so they absolutely can have behavior.
> Even in Smalltalk you see this issue happening. Ints and Doubles respond to messages to implement arithmetic, but for example reading an integer from a stream is a method of the Number class that takes an integer radix and a Stream as input. Why is it not a method of the integer radix or of the stream? Why can't different streams decide how to parse themselves into integers? Because the whole concept of associating behavior here is silly.
Actually, it makes much more sense than a `stream readInt` ever could. Why does a stream know how to read an integer? Why does it need to know how an integer looks, and how to create one? It is much more natural to ask an integer to take in a stream and create its own representation, IMO.