There is a bot script for completing quests in one MMO game, which I created and designed.
The problem is that I don’t like him, since I applied an anti-pattern (without realizing it then, it was a long time ago) a Divine Object ( God Object ).
How the script works
When I wrote it, I was thinking about the finite state machine, which goes from one state after performing the next step. If a step, say, 87 is completed successfully, then the machine will proceed to the next step 88. If the step of quest 87 fades (for example, another player attacks the bot and interferes with speaking with the NPC), the step will repeat and the machine will go back to state 87. In each condition is error handling and the JumpStep team, through which you can go from state 87 to 65 or any other (to handle a death in the middle of the quest).
Problem: how to implement all this in the form of code?
I really didn’t want to create an array of objects and functions or sculpt a giant case of (aka switch in C ++), because this all eats up the RAM + when I have to add a new method, I’ll have to edit this array / case. I solved the problem using RTTI chip pascal, through which you can call the method of the object by its name . The result was a class with a bunch of steps Step1 Step2 ... Step120. Inside the machine I keep the step number and when I have to go to the next step, I simply call with a code like: call_method ('Step'+intToStr(Step_Num));
In reality, the code looks like this (S is Step):
procedure TSE.S76; begin StepRes: = Go ('Schnain') and (Q ([NPC_Shnain, DLG_Ischeznuvshiy_Sakum__v_protsesse_, 1,1]) or WaitQS (Qu20, -1)); end; {----------------------} const Qu21 = 10334; procedure TSE.S77; begin StepRes: = Go ('Schnain') and (Q ([NPC_Shnain, DLG_Osmotr_kholma_Vetryanyykh_Melynits, 1,1]) or WaitQS (Qu21,1)); end; {----------------------} procedure TSE.S78; begin StepRes: = true; if PosOutRange ('Gludio', 5000) then StepRes: = Escape (TravelSoe); StepRes: = StepRes and Go ('Batis') and (Q ([NPC_Batis, DLG_Osmotr_kholma_Vetryanyykh_Melynits__v_protsesse_, 1,1]) or WaitQS (Qu21, -1)); if stepRes then prs ['DqusetWeapon']: = QEvents.LastItemAdd; end;
The solution is good in the sense that when adding new steps, it is not necessary to edit any cases and arrays. Just write a new method Step121 and that's it.
But I do not like this decision for many reasons:
All methods are in memory even when they are not needed. Yes, windows is cleverly arranged in terms of unloading / loading executable code pages from disk as needed, but I still would not want to rely on the OS.
Anti-faber "divine object" was applied, as a result, all the code was in one object, but I would like to find a good pattern for such a case that would elelegatno solve all my problems. It can not be that it was not, there are games like GTA - there are powerful scripted scenes, where with NPC anything can go wrong (like a car’s friend will block the path and he will have to bypass it or something else unforeseen ) and should be handling such cases.