@UltCombo Could I ask you try it now?
@Bryukh Yeah, with your newest patch the bombs trigger correctly when I jump. Howeverā¦ game session.
#
# Exploit #1.3
#
hero.moveXY(130, 10)
hero.jumpTo(hero.pos.copy().add(Vector(5, 0)))
hero.moveXY(center.x, center.y)
How!? How are doing this!?!?
Fixed. I increased the trap power. And radius.
Ahahah, I just enjoy finding creative solutions.
I also enjoy reviewing code quality and security.
Yup, it seems good now. You know, to be 100% failproof you couldāve added a āNo jumpsā goal and fail it once you detect a jump (using the same logic for detecting jumps that you have just implemented).
In any case, I cannot see any obvious flaw now. Good job!
I got to rest, but maybe Iāll experiment a bit more tomorrow.
First of all I would like to say hello to you, @Bryukh checkiO fan here
Regarding the level - I completed it the same way as your picture above shows. I havenāt used isPathClear as I knew about the part of ignoring traps from previous levels already, instead I wrote a simple distance function and calculated when I can go to the inner circle(no vectors involved). Speaking of distance function - Iāve only used distanceTo so far, but I think it could be quite a good idea to have one mission teaching younger players about it. It was really enjoyable level, I appreciate it greatly, thank you!
Thanks. You are an uber QA dev.
Hm. Itās a good point. Iāll use that advice for next advanced level.
Thanks! Now Iām here
Tssss Yeah, I solved it with similar method. But first I tried to build the full map of zones and implement A* search. It was too long and required optimisations. The next one idea - to create extended analogue of isPathClear
with rectangles. And while I was writing that I found the simplest method to solve that puzzle.
Hey @Bryukh. New Exploit!
while True:
flag = hero.findFlag("green")
if flag:
pos = flag.pos.copy()
hero.removeFlag(flag)
while hero.distanceTo(pos) > 0:
hero.move(pos)
Heh. Itās interesting. Looks like the flag appears and disappears in the same frame and itās not visible for referee in this case. But I think it can be checked in the non-existed thangs. Thank you again ā itās really useful for understanding of mechanincs.
Yes, I think you are right. From the observed behavior, we can determine that the game engine processes each frame in the following order:
handle input (flags) -> run player code -> run referee's `chooseAction` function
As an alternative approach, you could restrict the flags equipment for this level.
Hey, I was able to combine a few exploits and resurrect the jumping exploit. This is slightly more advanced than the previous exploits, so bear with me. I believe this exploration is useful for understanding the game mechanics as well.
Basically, here are the two exploits Iāve found:
-
Apparently, it is possible to get a reference to the original methods in the player code before you overwrite them in the refereeās
onFirstFrame
function. This works for thejumpTo
method, but it did not work correctly for thecast
functionāmaybecast
has something special, like having a ārecursiveā call tohero.cast
which then runs the overridden function? Not sure. -
I took a look at the refereeās code. It is only checking the heroās
z
coordinate while the guards are asleep, so it is possible to jump freely after waking them up with an alternative method (e.g. flags).
Hereās my game session.
#
# Exploit 1.4
#
hero.originalJumpTo = hero.jumpTo
center = Vector(68, 68)
hero.moveXY(125, 65)
hero.moveXY(116, 40)
hero.moveXY(100, 22)
for i in range(2): # Cancel inertia
hero.moveXY(76, 12)
while True:
flag = hero.findFlag("green")
if flag:
# Wake up guards
hero.wait(0.01)
# Go, Go, GO!
nextJump = Vector(74, 33.2)
hero.originalJumpTo(center.copy().subtract(hero.pos).multiply(.5).add(hero.pos))
for i in range(2): # Move twice to disable inertia
hero.moveXY(nextJump.x, nextJump.y)
while not hero.isReady("jump"):
hero.moveXY(78, 35.5)
hero.moveXY(70, 33)
nextJump = Vector(69, 53)
hero.originalJumpTo(nextJump.copy().subtract(hero.pos).multiply(1.3).add(hero.pos))
for i in range(2): # Disable inertia
hero.moveXY(nextJump.x, nextJump.y)
while not hero.isReady("jump"):
hero.moveXY(nextJump.x + 3, nextJump.y + 1)
hero.moveXY(nextJump.x - 3, nextJump.y - 1)
hero.originalJumpTo(center)
hero.moveXY(center.x, center.y - 2)
while True:
hero.shield()
Weāre trying to avoid it. It was a problem with my paranoia ā I checked only existed flags (The good habit for common levels)
Fixed that one.
Now your exploits are more complex than the solution
Thanks. Iāll explore that trick. But it can be broken (exploit) already, because I add āexplodeā for flags.
Itās interesting. @nick could you say something about it? I thought onFirstFrame
are executed before the player code.
Heheh yeah, I was mostly exploring the engine and possible flaws in the referee logic. This last āexploitā was mostly to demonstrate a logic flaw in the referee codeāchecking heroās z
only while guards are sleeping, given that they can be woken up by conditions other than changing the heroās z
. In any case, it would be sturdier to check the heroās z
and blow up the mines independent of the guardsā state. Or, always blow up the mines when guards wake up (i.e. all fail conditions trigger both guards and bombs), though that would make the guards redundant given the trapsā range.
The Wiki says:
onFirstFrame
This function happens exactly once, as well, but this time this happens after the hero is all set up and about to begin.
So I too would assume it runs before the player code gets to run. Letās wait for @nick or someone elseās input on this then.
Yep. I added āblown()ā near āwakeUpā. And I reduced diffZ
for jumps.
I left them as decorations.
I donāt remember exactly. I thought that onFirstFrame
was supposed to happen before player code started.
Hm. maybe some problems with ājumpToā, because that hack doesnāt work for cast
. I used the same place and method to overwrite them. UltCombo has found an interesting puzzle for us
I donāt think the problem is specific to jumpTo
. Although the exploit does not quite work for cast
, it does make a clear difference in the observed behavior: without the exploit the traps are blown up in the first frame (as expected), while with the exploit they explode several frames after the cast
invocation. Hence why I speculated this difference to be due to cast
ās implementation.
Well, I guess I may be able to create a new level as an isolated test case for this issue, mostly to exercise my level creation abilities as well.