January 19, 2019, 03:16:23 AM

Author Topic: Maze Raider - The aMAZEing code a game comp entry  (Read 3531 times)

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #15 on: September 05, 2018, 11:16:08 PM »
I looked at your code and ... it is not how I would like my code to be (I prefer to split things apart into multiple files for easier reusage in other projects, to avoid dependencies, ... and so on).
You seem to use many "procedural" approaches (defining things in the code rather than some .ini files - keep data separate from code). For prototyping I think this is good to go but hmm dunno about team-based development or if you want to overhaul the code later on (replace x with y).


Aside of that, I restructured my code the last 3 hours - to split functionality into separate classes and still avoid cludging everything in a "global"-script (available via "GLOBAL."). Still it seems you might rapidly end in a chain of children:
GLOBAL.currentLevel.gridMap.GetAStarPath(GLOBAL.currentLevel.gridMap, startVector, endVector, ...)
except you add helper functions
GLOBAL.currentLevel.GetPath(startVector, endVector)

Ok, so I got it working again and somewhen in the next days (next spare time) I will try to restructure the grid-manager part (grid cells + units). Collectibles should be easy to do too. Also I think animating stuff (like coins swirling into the air during collecting or so) should be easy to do: create the animation and play it once the player enters the grid of the coin/item/what-so-ever. Just need to research if I can reuse certain animations for multiple items (so relative changes like rotation, alpha, ...).


Progress for now: Some enemies (cloned from each other - so one script to rule them all) pursue my player character on a "per tile/grid cell" mode. Means they look for a new route once they reach their adjacient grid cell (the one bringing it a step closer to the protagonist/hero). When they enter a cell, they emit an signal, and when they leave, too.
Signals are used similar to "events": to decouple stuff (A does not need to know about B, B just registers to listen to A's signals/events).
Also creatures/units are now having a base class to extend from.


When I thought of "Godot" or "Unity" I hoped to have all these basic-stuff already done and just to have to add some meshes/models and to define the AI. Seems there is still a lot to do.


bye
Ron
 

Offline Qube

  • Administrator
  • Hero Member
  • *****
  • Posts: 1358
Re: [ WIP ] Competition entry: Maze Raider
« Reply #16 on: September 06, 2018, 07:11:04 AM »
Quote
Aside of that, I restructured my code the last 3 hours - to split functionality into separate classes and still avoid cludging everything in a "global"-script (available via "GLOBAL.").
Don't you find this approach to be the more snobby side of coders?. Those that go out of their way to avoid global variables just seems to be doing it because they can. Personally I find global variables invaluable, especially when just used as a flag across multiple functions.

Unless your aim is just to reduce masses of global variables?

Quote
I prefer to split things apart into multiple files for easier reusage in other projects, to avoid dependencies, ... and so on
How deep do you go with that?. For games I tend to just leave it at general framework, GUI, pathfinding, AI and common routines. I find it easier to adjust a common module as per needs rather than go super crazy and split things up into 20+ files. Sure I could split them up further but for simplicity it seems a waste of time.

How are you finding Godot?. It seems pretty well supported for an open sourced project.
Until the next time...

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #17 on: September 06, 2018, 09:17:42 AM »
@ Globals
I think it is kind of dirty if you have a "global" class containing the links/references/instances to all classes (level, grid, units) and to import these global into all other classes (so units can access "level", "grid").
I am used to think in parent-child-relationships. Parent knows that it has child, but parent does not necessarily have access to "parent.money" ;-)

So, maybe call it "separation of concerns" or whatever the right English terminus is.


@ splitting things
Not that deep. Tiles split from creatures, level base logic from level implementation ("the game scene" vs "level.GetUnitsAt(x,y)"). This is to allow individual levels to get added without duplicating the code.
In TVTower I split it way more apart as BlitzMax did not have "interfaces" but I needed a non-cludged access to information. I do not like to have "include" for everything but "import" (to speed up compilation times - and for easier maintainability). This "thinking" is burnt into my brain and so I try to use a similar approach in a dynamically typed language like "gdscript". Surely this costs a bunch of time and this is maybe wasted, dunno.

As said for rapid prototyping this is way too sophisticated and some might come way faster to an result then (on the first run - on the second project I can just add my previously created stuff and it hopefully auto-figures out what to do instead of having to adjust the code "here" and "there" and "ohh there too").
I mean: I once wrote an extendable GUI thingy utilizing some XML stuff. Why not just use this functionality, throw in an overhauled gfx and done?

Of course you are right when it comes to reusing game-specific stuff. So a pacman-creature-behaviour is surely not of use in a RTS game. These are not things I name "base.xxxx.bmx" ;-)
What I see as base is "tilemaps", "isometric grids", "highscore" ... and so on.

Now I started to try out godot, without knowing much how limited or unlimited it is, what is possible, what only with tricks, ... ?
I started and tried to keep things separate to maybe use it later on in other projects. Also I am not knowing if I do things wrong, so I better only do it wrong in one place (class, file, ...) than in a multitude of different scripts I "copy pasted" together from other scripts in the game.


@ Godot
It is interesting and of course I would more likely use it than "Unity" or "UE4". Think it is kind of an underdog. What I really miss is: written tutorials - or at least current 3.x tutorials. you find some interesting stuff for 2D (eg. GDQuest's youtube tutorial series) but for 3D you only find oddly-microphone-recorded videos in badly understandable English - or Turkish/Spanish (which is surely good for people understanding these languages!). Tutorials are often "long" or not strictly-to-the-point.
Just think of the latest Blender tutorials (jayanam makes nice little tutorials - especially good for beginners to Blender, and his German-originated basic English helps me to pretty easily understand him ;-)). These tutorials are exposing single stuff of Blender. Easy to spot them via the youtube search then.

But for Godot? If you lookup for stuff you most often land in the Godot docs - which are often just containing the function definitions (and description). It needs more examples.
If you want to check what Godot already supports in its frameworks (think of "gridmap" for 3D tilemaps) you only find some "minecraft"-like stuff.

Also Godot lacks some "showcase" games which are different to "2d/3D platformers". For now Godot seems more suited to replace old "GameMaker"-like games. Cannot imagine to do "TVTower" in Godot - except when using just scripts/code and skipping the whole editor-thing.

It is surely an impressive piece of software, but I think it needs a multitude of months to "mature" and to create a community being motivated to create tutorials. Maybe it needs some kind of "store" so people are encouraged to promote themself - and so indirectly their store assets / commercial tutorials / games. Blender achieved that during the hmm last 2 years, so this might be the timeframe for Godot to grow up too.


bye
Ron

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #18 on: September 06, 2018, 01:33:42 PM »
Spend this lunch time to research about signals.

Seems signals are ... kind of limited. So you need a "sender" and a "target". There is no "broadcast" (I thought it behaves that way - but only the sender received the signal, which is pretty dumb).
Seems the other way to broadcast is available via adding objects to groups and then call a function in each member via "call_group".

They say signals decouple logic in godot. Nope, it does not decouple as I understand "decoupling". if A wants to listen to B's signal, it must directly link to it, so it must know about the existence of B. And if C does send the very same signal too, then A needs also to know about C. This is not "decoupled".

So this means for example: player A is the city-boy visiting a ranch and does not know much about animals, and then there is cow B and C which are eating grass. Now they want to inform the world about the fact they eat grass.
- A could now iterate over "cow herd" and check if they eat
- A could register for the signal of cow B that it begins to eat - but he does also need to register for the signal of cow B, C ... Z1000
- in a better world (eg in my Dig-Framework@BlitzMax) he would register for an event "animal.eats.food" and gets informed about all animals sending out the event, he does not need to know if it is a cow or a hen (but he could check via the "sender")
- Godot allows (as said) to send to groups but you need to take care of having items in the right group

Any ideas how to do "#3" - or is "#4" very similar?


To explain what I want to achieve (for now):
- each collectible, unit, ... "level object" sends out an event/signal/... when another object enters/leaves the very same grid cell
- each of these objects listens to these signals and handles if another object with the same col,row emitted an "enter/leave" (they then emit an "collide" event)
- objects are free to handle these collide-events/signal/... then (eg if it is a "player vs coin" the coin would get collected and score is given)

While one could handle it differently (Players/Creatures know about items/coins/...) it can create issues (Players need to know Creatures during collision and Creatures need to know about Players ...).
Will try this evening if "groups" could inform each other ("unit enters grid cell", col, row) and so on. It feels like using a crutch instead of a clean solution. But hmm, maybe I am the one doing it wrong. Maybe I just should use simple "if sender is Player"-checks and skip all these sophicisticated "separate concerns" thing. Hard to convince myself. hmm.
 


bye
Ron

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #19 on: September 09, 2018, 09:36:03 PM »
Did not have time to do something today so I was only able to record a video from yesterday's after-lunch-session:



What you see there ware the newest additions:
- restructured "map objects"-managing
- - adding them to (multiple) godot-groups (for potential message-broadcasting)
- - added them to manager-groups (so I can easily pre-count how many items, collectibles, ... are on one tile/grid-cell)
- added some test animations (to try how "chaining" functions) for collecting, idling ... of items/collectibles
- tests for AStar-following creatures (same temporary-character, sorry ;D)
- no longer using signals for now as they are just not useable for my intends, am checking for collisions "by hand" now in the level class (creature vs creature, creature vs items/collectibles)

Most CPU utilization comes still from the lighting (20% without lights, >50% with lights). The whole handling of the grid logic is not adding much CPU wise - so I assume there is no need to optimized that yet.


Next I want to try out on how to adjust alpha of objects, do not plan to do that via shaders...
Once this is working I need to add some kind of "switches" so manipulation of the level is possible (opening paths, ... ,doors, ...).

Also thought of making the level even darker and give the main character a torch then - think that could look cool (and might be of use to fight mobs?! - in that case spots in the level might be existing to "re-fuel" your torch). But for now: trying to keep things simple and "finishable" but I bet, you all understand that "tinkering" and disuccing "ideas" is one of the cool aspects of game dev - the things that make "fun".


bye
Ron

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #20 on: September 15, 2018, 09:00:15 AM »
Used yesterday's evening to replace a lot of map/grid code. Instead of using various objects to have "collision maps" (grid blocked or not) and "object maps" (storing a dictionary/table for each cell containing objects) I use a similar approach to Mattys now: each grid has a list of layers (used in that grid) and each layer can contain objects.
I used the prior approach to "split" the code but as everything knows all other things there is no need to make it that complicated.

But still the whole bang took a lot of time - why? Dynamically typed languages...
If you refactor your code, eg you replace an "integer array" with an array now containing objects it wont fail during "compilation" (which it surely not really does - it only syntax checks). It will error out when it uses that line of code the first time. So until eg. no collectible is collected, the game will run - but once you collect one, it errors out as you still use an array as "integer value" instead of the object now.

So everytime you change something, you might end up with an error somewhen in your game as you missed something. This is pretty annoying.


Also: assume you delete an object in your code (via "queue_free()" - to only finish it when it is idling/not processed at that moment). Now you had a reference to this object somewhere in your code (so not in the "godot maintained" stuff like the "node tree"). Now you iterate over your custom list containing the reference to the object. You even check for "if object == null" ... but it still fails then with some "accessing function of previously freed object". I mean, couldn't it just say "object == null" at the first glance?

Also: objects extending from the class "object" (so "node", "spatial", ... and whatever else) have methods like "has(value)" to check if an object has a "var myvariable" (or "global") defined. It also has "has_method(value)" to check if a function was defined in that class. This works nicely - but not always. Sometimes I pass a "spatial" (a loaded scene with a "spatial" as parent) to a function - and it then errors out (of course just during runtime) that the passed "object" does not have a function "has" (or "has_method"). The Debugger then says "object1234" and no further properties (no "variables").

Ok, maybe I really passed something not containing that functions? No clue, still newbie for Godot. But hey, wait. You can do if ("value" in object) to check if it has a property - and guess what, it now found my property without erroring out.
So what did I do wrong?

Means during refactoring the code I fighted the language for a lot of minutes - minutes I would better have spent for the game.



During refactoring I also added some helper methods to replace "floor + layout" tiles (the "floor" is a separate scene/object) with others. Means I can now add traps (having it's own "floor" - so I can eg destroy it partially for a "falls into trap" effect) to a grid cell dynamically.
Needed to do such stuff so "moving tiles" are easier to achieve.
Also created some "blocker" items (walls coming out of the ground once a switch was activated). Code preparation done to make dynamically "IsTileBlocked()" checks possible (all objects attached to certain layers of a tile are asked if they are blocking).


For now I really enjoy things like "dictionaries" and the likes - they ease a lot of pain during coding (but they of course feel somehow "dirty" and "expensive"). But I really dislike the somehow inconsistent behaviour of the scripting language. Also the dynamical typed language aspect is really... something I have to get used to - especially as it is surely expensive to always check a passed parameter first (not just "if bla == null" but if it is having a specific method).


bye
Ron

Offline Matty

  • Hero Member
  • *****
  • Posts: 584
    • MattiesGames
Re: [ WIP ] Competition entry: Maze Raider
« Reply #21 on: September 15, 2018, 08:36:40 PM »
Nice to know my approach came in handy.

Let's see some more screenshots.


Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #22 on: September 20, 2018, 10:16:33 PM »
Pheew... two evenings to get some kind of "pac man"-level-like generator up.
Not really satisfied as it (for now) looks too "clean" for the game theme. So a "dungeon"-like-thing might suit a bit better.
I have the whole map generator in an extra-script so it should be fairly easy to replace it with another one (or offer both).


(yeah, I miss some blocks for the "4 walls = 1 block"-parts, will replace with some Egyptian statues or obelisks or so)

Performance of the generator is far worse than when writing such stuff in BlitzMax. Of course this might be grounded on the way I do (try) things in Godot. Nonetheless we talk about milliseconds in these small levels (I tested with a bit "more" cols*rows ;-)).

Another thing I tried to tackle for about an hour was: how to draw 2D stuff on 3D things. At first I added a "Spatial" and there I added some TextureRect-nodes. These TextureRect nodes can emit a signal when they "_draw". My main script hooks into it and calls some custom draw stuff. So it drew the "minimap/map-layout". Nice. But ... oh, ahh wait, why was the 3D part drawn after the minimap? Hmm, I moved/adjusted-add-location of all 3D objects into a new node ("playground"). I moved the "Spatial" (containing the TextureRect) after this playground-node but hmm, did not work. No clue how to set a z-index for these things.
Googling for "Godot HUD" or "Godot draw 2D stuff" and so on ... leaded to 2D Godot tutorials, not "3d + 2d" things. Somewhen I found a Godot documentation telling something about "CanvasLayer" - which allow some "Layer setting". Voila, added such a thing as parent of the TextureRect and was able to set the layer to "1". Now my hud/minimap was rendered after the 3D stuff.


Pheew .... I really thought that things are way easier in Godot (or Unity?). It is not enjoyable if you need to lookup soooo much stuff instead of just creating your assets, writing some logic and voila. Man, I hope I will once reach the part in the project where I can add some animations (Hooray "game won", "Ohh noeees I die", ...). Think they are more fun then.


bye
Ron

Offline Qube

  • Administrator
  • Hero Member
  • *****
  • Posts: 1358
Re: [ WIP ] Competition entry: Maze Raider
« Reply #23 on: September 20, 2018, 11:29:27 PM »
Quote
Pheew .... I really thought that things are way easier in Godot (or Unity?). It is not enjoyable if you need to lookup soooo much stuff instead of just creating your assets, writing some logic and voila. Man, I hope I will once reach the part in the project where I can add some animations (Hooray "game won", "Ohh noeees I die", ...). Think they are more fun then.
I guess you are plodding on as slowly as I am? :P - Certainly a steep learning curve diving into a whole new way of working. Every step of the way for me too has been google / youtube.

I think people think that Godot / Unity make things so easy and truth be told, they don't. Sure, it's nice to visually build your scene / level but in the end you're still coding your game logic in C# ( not sure what Godot uses ). For me it's been a real hard slog at every step but I'm funnily enough enjoying the challenge ;D - At least for my next game I'll have a much better knowledge on how to approach things.

How are you finding Godot overall?

Back on topic... Your game is looking pretty swishy and intriguing. I hope you get to finish in time :)
Until the next time...

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #24 on: September 21, 2018, 07:08:55 AM »
You could use mono/c# for coding - or their "gd script" which is very similar to Python - which means you always get issues when you paste some code with "space intends" instead of "tabs" (eg. you wrote something in another editor - or you paste some sample code). Happens not so often (as you most of the time write your code straight in the inbuilt editor) but as the editor lacks some replacement/refactoring-skills you here and there get the wrong-intent-error.

What bothers me most is the lack of "keywords" for proper google/youtube-search. I cannot watch every "Godot" video just to somewhen find what I currently am struggling with. They do not use the words/terms _I_ would use. It is some kind of "hen - egg" problem. If I knew the right functionality/keyword in advance I just could lookup the documentation or use it right away. As you do not have proper autocomplete for your objects (dynamically typed...) you would need to tangle along the whole hierarchy of object class ancestors until you reach eg. the functions doing vector normalization or assigning materials ...

As said: the projects (Unity, Godot, Unreal, ...) are surely way matured than many libs/frameworks but if you do not know how things are called there... you are pretty much lost.


I still cannot see myself writing "TVTower" in Godot/Unity. As someone else stated in this forum: Unity/Godot are surely suited for arcade games or maybe jumpnruns.
The editor is then used similar to "effect editors" or "tile map editors". Sure you speed up stuff there - but most often you only help a designer, not the coder.



@ game look
Nah, once it works I would of course replace my mockup assets (or improve them). Walls need rounded edges if they are not connected to another wall. Decoration elements are missing, items are missing.
This will be the fun part. Designing golden scarabs (or so), modelling and animating mummies. I feel way more "welcome" by Blender than by Godot - for now.


bye
Ron

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #25 on: September 25, 2018, 09:51:50 AM »
Spend yesterdays evening (2 hrs after some wine and a movie with the wife - real life matters ;-)) to get used to particles in Godot.
I tried to find a way to randomly rotate a plane (quad) particle around its center - so that it is visible from every angle (here and there).

Seems Godot does not have that option (at least it seems I was not able to find it ;-)). Tried to use qubes but that looked ugly too ... with my current configuration.
Ok, tried to find stuff on google/youtube: nada. I only saw people presenting their _results_ (not the setup/configuration) of a simple fire animation.

After tinkering about a "always face to camera" thing I found billboards... weren't billboards always looking into the camera? Hmm, has Godot such a thing? Yeees it has - hidden in some option panel of the texture/material: "Parameters :: Billboard Mode :: Particle Billboard". I mean - if THAT wasn't obvious. I am glad that the Godot documentation of the year 2019 had this information right at hand.... *grrr*.

Ok, seems my particle planes now faced properly to the camera. Needed to get my textures working. Set them as transparent (as before) but it all looked dull when fading out alpha the older the particles got (top of the fire). Tried to use a multiply/add "emission" setup to brighten things up --- naahhh looked ugly. Tried out to use "transmission" (I thought the alpha values already configure a transmission ...) and voila, it looked _pretttttty_ cool!. Transmission seems more a kind of "screen"-mode in Photoshop (grey + grey becomes light grey). With the right "color" (which seems to be a kind of "Multiplier" between 0 and 1 - allowing images for varying multipliers) it was easy to adjust it properly.


For the creature (for now still the player mockup character) I added 3 particle systems - 2 on the arms and one at the back. Then I emit only 2*10 and 1*20 particles (=40) per second - think that is a low value.
To make it look "burning" I also added to small lamps in front and back of the character. The lamps are also animated like the torches in the corners of the level. This makes it "flicker" during walking. Maybe I attach the lamp to the movement of the arms too (will have to check how to do this - maybe some parenting thing (empties or so?)).



Using multiple particle systems allows me to animate the engulfing-process. Means: you somehow attack the enemy (or he walks into a trap) and then you start particlesystem 1 ... after 0.5s system 2 and so on. This then looks like it starts to burn.

I think I could also add all the particle systems into a new scene - and append it as needed to multiple objects (means I only set the effect up once and use it multiple times) -> multiple creatures use the same effect setup (but with their own particle instances).


bye
Ron

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #26 on: September 25, 2018, 12:28:51 PM »
Ok ... so how to adjust particle systems from within your code? How are the functions or properties named? The documentation talks about particles or particles2d - but the particle system (3D) itself?
A dynamically typed language does not autocomplete well... so "Particlesystem(dot)" does not open a list of suggestions. "Emitting" is it labeled in the editor, tried "particlesystem.Emitting = true", null-access-error. Tried "particlesystem.emitting = true" and it worked.

I know I could trigger certain stuff via "AnimationPlayer"-Animations (so instead of changing values you call a function after X seconds or so") but for now I just did a "StartFire"-Animation which animates the "visibility" of the lights (emulating the fire-lighting). Also I animated the power of the lights (from 0 to the value used in the fire-light-animation which is played in a loop). Still experience some odd "shadows" (maybe the lights need to be repositioned aside of the particle system ... I already intended to move the lights too, to make it look more "fiery").



For fire it is also important to set the coordinate system for the particles from local to global (once they left the body, they wont move along with him) ... "let it go, let it gooo..."


bye
Ron

Offline Derron

  • Hero Member
  • *****
  • Posts: 1553
Re: [ WIP ] Competition entry: Maze Raider
« Reply #27 on: September 26, 2018, 12:08:03 AM »
Added "spread fire" so it is up to you to create a nice chain of burning stuff.
Needed to fiddle around with "previously freed instances" - you need to take care on your own of getting rid of old variables - as there is no way to check for "valid instance" yet. Assume you have a coin object in multiple "node groups" and set the coin to "queue_free()" (remove as soon as idling) and then you access it via one of the "node_groups" the entry is not null, but leads to an "previously freed instance" error.
Means you need to manually remove all the stuff and so on. Can get tedious.

Whole code looks more and more "dirty" with long lines just checking if a given object is of a certain type/class/has functionality. Refactoring becomes a hassle!


I also changed the fire animation to be more "comical" one as I assume not all people like to watch stuff burn.

Nonetheless: short video



bye
Ron

Offline GW

  • Full Member
  • ***
  • Posts: 151
Re: [ WIP ] Competition entry: Maze Raider
« Reply #28 on: September 26, 2018, 05:20:32 AM »
Looking good!

Offline Qube

  • Administrator
  • Hero Member
  • *****
  • Posts: 1358
Re: [ WIP ] Competition entry: Maze Raider
« Reply #29 on: September 26, 2018, 07:44:57 AM »
Quote
Needed to fiddle around with "previously freed instances" - you need to take care on your own of getting rid of old variables - as there is no way to check for "valid instance" yet.
Doesn't Godot have a method to spawn / deleting instances of objects? - Surely it must have a method to create and destroy multiples of an object? - That's kind of the basics of any game.

Quote
Assume you have a coin object in multiple "node groups" and set the coin to "queue_free()" (remove as soon as idling) and then you access it via one of the "node_groups" the entry is not null, but leads to an "previously freed instance" error.
That sounds like more of a bug to me. If you have a script attached to an object then it shouldn't run if that object doesn't exist. Unless you are accessing them via another script to which I would say it's script priority error. The code side of Godot/Unity are very OO based so keep in mind that one script may delete something but another may try to access it in the same update sync.

Also ( don't know if this applies to Godot ) keep in mind that there may be two updates going on, normal per frame update and physics update. If you want to access other objects then keep all that code in one or the other. Mixing between the two will lead to referencing issues.

Quote
I also changed the fire animation to be more "comical" one as I assume not all people like to watch stuff burn.
I think you could get away with them screaming in a high pitched comical voice. If you were going for photorealistic graphics and people burning then I think some would find that awkward but a comical cartoon style (thankfully) is a still a safe haven to have fun.
Until the next time...