Sunday, July 21, 2024

map poked!

map.poke is done. Okay, it doesn't read "map.poke" in the level script but rather block (coordinates) = (hex) so that it mimmics the block (hex) { ... } definition. And that makes it specific to special blocks.

But there it is. After some branches untangling and more commits weaving, I could use it to add a secret exit in the trunk of the 'greent' room without having to go through the now-clumsy steps of patching the level on NDS while editing code on the PC.

Bon, je crois qu'il n'y a pas besoin que je vous dise que quelque-chose n'a pas marché, hein ? vous vous en doutez rien qu'en regardant l'image... En mars, j'avais évoqué la possibilité de reprendre les vieilles maps pour étoffer un peu "Dreamlands" (ou à tout le moins, proposer un peu plus de contenu pour la démo "3 rooms"). Et bonne nouvelle, j'avais fini par retrouver les maps en question. Je m'étais donc rajouter un peu de script qui dise "alors, le bloc de type 11, c'est une sortie secrète, et dans le niveau greent (le nouveau), si on la touche, tu charges le niveau greeny (l'ancien)". Sauf que pas moyen de tester, évidemment, parce que sur greent.map, il n'y a aucun bloc de type 11...

C'était donc l'occasion de se bouger un peu et de coder le mécanisme dénommé "map.poke", à savoir permettre au script d'aller modifier les propriétés d'un bloc du niveau sans rien demander à personne (et en particulier pas à l'éditeur de niveaux ;P). Et oui, la vie est telle qu'il m'aura fallu un bon mois pour que ces malheureuses lignes de code finissent compilées sur le PC qui avait aussi le fichier greeny.map ce matin. Tout ça pour se rendre compte qu'avant de pouvoir faire une "release" avec la vieille map n°1, je vais devoir lui faire un sérieux ravalement de façade pour l'adapter au nouveau tileset ^^"

Oh I presume you don't really recognize the big tree of my so old two-levels demo: since I re-structured the tileset, there are quite some editing to happen there before you can actually enjoy anything :P

Friday, July 19, 2024

Soudliers (encore)

 mais cette fois ci, je suis aux manettes :P

Le jeu est de toute beauté, et je dois dire que le level design me convient assez bien. J'avais un peu joué en fin de soirée, j'avance tranquillou (je me suis en mode "exploration", pas fou), mais je trouve prenant en ayant suivi un tunnel pour me retrouver dans une chute d'eau tout en sachant que j'ai laissé une autre route derrière moi avec cette sensation de "mais alors ? qu'est-ce qu'il y avait de l'autre côté ?_?

Je joue l'archer, aussi. Le jeu a la réputation d'être crapuleusement dur alors fort de mon expérience dans Shantae, je prends un perso qui peut attaquer à distance.

Deux boutons d'attaque - une flèche instantanée mais qui nécessitera un recovery plus tard, une attaque tournoyante de portée plus faible et qui nous prive de tout autre attaque jusqu'à ce qu'elle soit finie mais qui peut être enchainée à l'infini - un bouton d'esquive pour contourner un adversaire - que pour une fois je parviens à utiliser pas trop mal - et bien sûr un bouton de saut. Un panel de mouvements qui n'est pas sans rappeler le combat dans HoB, soit dit en passant. Tout le reste sera sur les gâchettes ou les entrées directionnelles supplémentaires des joy con. Et du reste, il y en a, comme le lancer de bombes que j'ai dû remapper parce que je le déclenchais à chaque fois que je voulais utiliser le bouclier (qui pare entièrement les assauts mais qui fait baisser la barre de statut mauve.

Il n'y a pas à dire, les mécaniques de gameplay sont riches, plutôt bien équilibrées. Mon seul coup de gueule c'est cette "roue des compétences" qui me permet pour l'instant de choisir entre les armes de base et les armes de feu (ouh, J.L.N va adorer, ça) qui est mappée sur le stick directionnel de droite et que j'accroche donc accidentellement quand je tente un combo "sauter/tirer" trop souvent à mon goût.

Les niveaux sont immenses, mais un mécanisme de téléportation entre points de sauvegarde est proposé d'entrée de jeu ... heureusement. Seul point noir, le temps nécessaire pour les sauvegardes et les téléportations (ou les changements de maps, d'ailleurs).

edit: je suis arrivé dans la pyramide qui m'intéresse lors du premier week-end de jeu. Le mode "explorer" porte bien son nom, même si je dois quand-même sortir une potion de soin de temps en temps...

edit++: au bout de 8h30 de jeu, un boss atypique dans le fond de la pyramide me met finalement game over en boucle. Il m'aura fallu un moment d'étude de let's play et beaucoup de chance pour le vaincre.

Tuesday, July 16, 2024

How GEDS scrolling works

The scrolling routines of InfiniMap weren't as well documented as I hoped. Not even when I blogged that "yeah, it finally works". And while trying to document them, I noted some undesired difference between what the code does and what I claimed it does in the comments. So here it goes. First this is the full level map, stored as a 2D array of tiles. Within them, we define an area of world coordinates (xmin,ymin)-(xmax,ymax) that is guaranteed to be present in the video memory as well. That is the "invariant", the condition that was true on the first line of a scroll_*() call and that must be restored before we leave the function.

if (py < ymin + WH / 2)
do { up_scroll(); } while (py < ymin + WH / 2);
else if (py + WH / 2 > ymax)
do { down_scroll(); } while (py + WH / 2 > ymax);
if (px < xmin + WW / 2) left_scroll();
else if (px + WW / 2 > xmax) right_scroll();

WWxWH is the dimension of the validty area. We will never let xmax - xmin < WW happen. To decide whether scrolling routines should update the contents of video memory, the scrolling code compares the position of the camera (i.e. the center of the viewport) with the coordinates of the area. If it gets too close to one edge of the 'validity area', video memory contents will be updated and that edge will be moved, possibly dragging the opposite edge along (since we have fixed-dimension video memory)

void right_scroll() {
unsigned xoff = (xmax - xmin) / TL;
// compute target position in video memory
u16 *src = dataview + xoff;
xmax += SQ; // new validity window will end 64 pixels on the right.
if (xmax - xmin > 512) {
xmin += SQ;
xview = (xview + SQ / TL) & 63;
dataview += SQ/TL;
}
// copy from src to video memory
// so we fill the expanded area with valid tiles. 

Then, this is the video memory. The hardware reads it as a 2-dimensional wrapping array, that is if it cannot read more at the right, it goes back reading on the leftmost column. The (xview,yview) reference point corresponds to "where in the video memory is (xmin,ymin)" (that was the part that needed a patch).

Note that only a part of that valid area will show up on screen (highlighted here).

Note that whatever happens, xmin always shifts by 64 pixels. That's the horizontal copy granularity SQ (because it was meant to be square), and no, I never checked it led to better performance than 32. We must have ScreenWidth + 2*SQ < VramWidth, though.
 

Saturday, July 06, 2024

Test tile type from state transition

As I update an old post to explain that yes, cando() finally managed air/water transition I note that the whole blog is still missing a key explanation of how this was made possible. It happens in state machine transition expression (in the predicates, actually) and says "tell me the properties of the world n pixels above my hotspot".

$FALL->$INWATER on fail [2 H WATER ?]
                        (v1 v5 + 2 / :1 0 :5);

$INWATER->$RBOUNCE on event0 [D_FOOT 8 H AIR ? &]
                        (800 ~ :1);

You see it there. H is the new OP_HOTTILE for the GobExpressions. it picks the value on top of the stack, the constant 2 or 8 here. We can then test bits against the constants WATER or AIR with the ? operator.

Without that, the trick about a slice of tiles that are both water and air is useless, because you couldn't make the difference between falling into water and falling on the ground anyway.

(and yeah, I still have to add those get-worldwide-flag and set-worldwide-flag opcodes as well as the roll-the-dice opcode)