So, Water Guardians (among other creatures) are powerful beings that can grab and pull unsuspecting adventurers if they stray too close to water. They aren’t the only creature that can pull others, but they’re exceptionally strong at that and can pull being equal to their own size (and since they’re large creatures, there are very few things that can resist that).
I have had size attribute for characters almost from the beginning, but I haven’t used it for anything. First step was to create accessors for it and codify what are allowed values. If I have a list of allowed values and accessors enforce them, I hopefully can catch typos and other silly mistakes easier:
(setv sizes ['tiny 'small 'medium 'large 'huge]) (defn size [character &optional size] (when size (assert (in size sizes)) (setv character.size size)) character.size)
Sizes is just a simple list of symbols in ascending order and size function can be used to both set and get size of a given character. I like building combined accessors like, although I have recently been wondering if I should return character instead of size when setting it. That way it would be easier to chain multiple methods modifying a character together:
(-> (name "Pete" character) (age 25) (size 'medium))
Since sizes are in ordered list in ascending order, it’s easy to compare two and see which one is bigger or smaller by comparing their index (this will of course fail if given size isn’t in the list):
(defn smaller? [a b] (> (.index sizes a) (.index sizes b)))
Now that we have some code (or before we have this code, depending on which way you like to do things), we should state some facts about it and see that the implementation matches to our idea. I’m using Archimedes for writing simple tests like these:
(fact "tiny is smaller than small" (assert-that (smaller? 'tiny 'small) (is- (equal-to true)))) (fact "large is not larger than huge" (assert-that (larger? 'large 'huge) (is- (equal-to false)))) (background default [character (-> (CharacterBuilder) (.build))]) (fact "setting and retrieving character size is possible" (with-background default [character] (size character 'tiny) (assert-that (size character) (is- (equal-to 'tiny)))))
While these are really basic tests and I don’t really expect them to catch any bugs, I still like doing them and building a safety net for those times I make a mistake. It’s much easier to build software from small components that are working correctly and that you know are working as you want them to work. It also serves as sort of documentation for future you about how things are currently working. Written documentation is perhaps nice to look at, but executable code is better way enforcing that things are actually working as documented.
So, back to our question, who can pull and what? Characters who are larger than target they’re trying to pull, are capable of doing so (for now. This will probably change and evolve more corner cases as I progress through writing the feature). With our language about sizes and their relation to each other, we can write that as:
(defn+ can-pull? [character target] (larger? (size character) (size target)))
defn+ is extended version of defn that makes defined function accessible to call macro. This allows various rules packages call each other, without having direct reference. And in turn, this indirection allows replacing rules with other versions easily, without having to make changes all over the system. Drawback of course is, that it’s hard to see from outside, what exactly these methods depend on. I decided that added flexibility warranted this drawback, as long as defn+ defined methods aren’t sprinkled all over the codebase, but limited mostly to rules package.
Later on I’m planning on adding size attribute on items too. At that point, the code to check if character can pull an item will automatically work and use exact same rules as for creatures. I might make it easier to pull objects as they’re inanimate and not fighting back, but that I’ll tackle in the future (as well as dragging an unconscious character).