M
M
mikserok2015-05-03 08:23:24
Delphi
mikserok, 2015-05-03 08:23:24

What template should I use for a quest machine with a large number of steps?

There is a script for a bot, for completing quests in one MMO game, created and designed by me.
The problem is that I don't like it, because I applied the anti-pattern (without realizing it, it was a long time ago ) God Object .
How it works
When I wrote it, I was thinking about a finite state machine that transitions from one state after performing the next step.
If step 87, say, succeeds, then the automaton will proceed to the next step 88.
If quest step 87 fails (for example, another player attacks the bot and prevents you from talking to the NPC)
then the step will be repeated and the automaton will again go to state 87. In each state, error handling is possible and the JumpStep command, thanks to which you can go from state 87 to 65 or any other. (for handling a death case in the middle of a quest)
Problem: how to implement all this in 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 it all eats up RAM + when you have to add a new method, you have to edit this array / case
I solved the problem using the RTTI Pascal chip thanks to which you can call an object's method by its name . The result is a class with a bunch of steps Step1 Step2 ... Step120 .. Inside the automaton I store the step number and when I need to go to the next step I just call
code like: call_method ('Step'+intToStr(Step_Num));
In reality, the code looks like this (S is for Step)

procedure TSE.S76;
begin
    StepRes:=Go('Шнаин') 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('Шнаин') 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('Батис') 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, you do not need to edit any cases and arrays. You just write a new Step121 method and that's it.
But I don't like this solution for many reasons
: 1. All methods are in RAM even when they are not needed. Yes, windows is cunningly arranged in terms of unloading / loading executable code pages from disk as needed, but still I would not want to rely on the OS in such a matter.
2. the divine object anti-pattern was applied, as a result, all the code is in one object, but I would like to find a good pattern for such a case that elegantly solved all my problems, it can’t be that it didn’t exist, there are also games like GTA, there are also powerful scripted scenes where everything is with NPCs anything can go wrong (like a friend of the protagonist will be blocked by a car and he will have to bypass it or something else unforeseen) and there should be a handling of such cases.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
K
Kano, 2015-05-03
@Kano

The best way to learn is to look at analogues of already existing bots, for example, the buddy bot family - https://www.thebuddyforum.com
Written in c#, not obfuscated. There are many extensions and profiles with bot behavior logic

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question