December 7th, 2009
So I started over from scratch again! Kind of. My plan is to have something marginally playable by the end of the month, so I'll find out very soon how well that plan is going to work. The first few days were easy sailing; mostly just welding together various subsystems I had already written a while back. Yesterday I started work on loading from config files again, and today I'm picking over the code— I stopped last night while working on a crashing bug related, somehow, to how I stored option data. Long story short, most of the problem was due to be using some old code I had written and never tested, and thus was ridden with bugs. There are still some memory leak problems, though.
No screenshots this update, because despite the whole interlinked input/event/animations system I have, there's absolutely no rendering or state code yet. That comes after I finish the options, when I start working on UI.
December 9th, 2009
I don't want to talk about it. (And I still don't know what's causing the problem.)
December 10th, 2009
That was, by far, the most intensive debugging session I've ever had.
So, as you might have gathered from the terminal screenshot yesterday, I was having memory corruption problems. I WILL GO INTO DETAIL HERE, so the less technically minded can understand the problem.
Memory, on a computer, is divided into addresses. They are in a long, one-dimensional line— think of very, very long straight road, with houses all along one side. Their house numbers would keep getting bigger and bigger until, eventually, the road ends. FURTHERMORE, for reasons of artifice, the house numbers would not increase by two with each house, they would increase by eight. So at the very start, you'd have 0, 8, 16, 24, 32... and so on. In this case the word size is eight bits. In the terminal screenshot, all those 0x996....
numbers are memory addresses.
MORE IMPORTANTLY is how memory addresses are used: when you code and you need memory, you ask the computer for a chunk of memory and it will give you an address, which has all the available memory you requested (or 0, if there's no memory left). And you're not limited to only asking for a single word of memory— if you ask for 4 bytes (32 bits) you'll still only get one address back, but you'll have the keys (as it were) to not only the house whose address you got back, but the next three consecutive addresses, as well.
If you have one address, you can easily reference to its neighbors using array notation, so that if you have a pointer * ptr
with an address in it, ptr[0] is the address, ptr[-1] is its previous neighbor, ptr[1] is its following neighbor, and ptr[2] is the neighbor past that. More about that a little later.
This is complex and there's a lot of design code, both in the system and in programs themselves devoted to keeping track of just what memory they have requested and gotten permission to use (the technical term for that is allocated). For phantomnation, I have a pretty simple system that tracks every time the program allocates memory and every time it frees memory (which returns the keys to the houses back to the system, so you can't check what you've stored inside that address) and makes sure all the addresses allocated are freed by the end of the program.
But anyway, this problem I was having. It showed up when I finished the sprite system; a neater recode of the system I coded this past April. There's a big complex state machine going through images, reading the pixels and storing away sprites, each one allocating a few addresses worth of memory for the sprite data.
At some point in the process, though, old sprites started having their data overwritten— that's why there are random gigantic numbers in otherwise empty sprites in the screenshot. Something that the program was doing during the state machine loop was overwriting memory. This was unusual, because while the state machine is complex, it never did the sort of complex pointer arithmatic that could possibly do something like that. Which meant it had to be one of the subsystems!
The only lower-level functions I was calling during the state machine loop were about lists— basically a helper structure I wrote so I could reliably tack an arbitrary amount of data to the end of an arbitrarily-sized array and not have to worry about overrunning the memory I had allocated. And so while the list code could overrun memory, well, not only was it expressly designed not to but also it's used all over the program, so I'd think any problems with it would have become obvious before now.
However, the one thing about the sprite code was that it was large. If there were bugs in the list code that only became obvious when one filled up with several hundred entries... then the sprite code would be the first time a problem like that would become visible.
There were problems with the list code, of course. That's actually what I was complaining about on the seventh— I had written the list code months back, but never finished it. When I started using it, assuming it was complete... there were problems. But I had fixed them already! There was nothing left to fix!
Except, I could track just when the memory would get overrun, and it always happened right after a call to the listAdd()
function. Which wasn't even doing anything complicated! Well, to be technical, it happened every time the state machine hit a corner— that was the signal to allocate memory for a new sprite, store it in the sprite list, and do a little fiddling with the state machine state.
The list code... it wasn't perfect, but even after tracking its every memory write, there was no way it was causing the problem. So this morning, I decided to go deeper.
The memory allocation code is based on the list code— it's set up to keep a self-resizing array of memory that stores all of the memory addresses allocated by the rest of the program, so it can list off what hasn't been freed. It can't use an actual list, though, because actual lists allocate memory using the memory allocation code, so that would start a recursive spiral of death.
And the memory allocation code... well, the first thing I noticed was that it was terrible. Apparently at some point, I had forgotten that the memory structure stored pointers to memory structures instead of the structures themselves, so it allocated twice as much memory as it needed for the actual self-resizing array structure. However, fixing that bug just made the program crash sooner.
It wasn't until I referred over to an older and inefficient (but proven non-buggy) version of the memory allocation code that I realized the problem: when the memory array filled up, it allocated a new block, twice that size, and copied all the data over to it. Then it freed the old data and replaced the reference to the old address with the new one. However, at no time did it resize its counter of how much space it had allocated!
Since it started with room for 32 entries, which was actually room for 64 because it allocated too much space, and then hit that limit and resized to room for 64 entries (which was actually room for 128), this bug would only show up when I had more than 128 addresses allocated at one time. After it resized once, since it still thought it had 32 entries with 32+ filled (I used a filled == size
test, not filled >= size
) it never bothered to resize again, and so eventually overran the memory storage list and started writing to whatever addresses were past the end of its space— which, in this case, was one of the many allocated sprites!
So then I fixed the memory system, made it resize itself and properly zero-out old addresses and only keep as much space as it needed. And now that problem is fixed! The end.
December 11th, 2009
font code try #1: i forgot to enable textures
font code try #2: the baseline calculations are all wrong, and it becomes obvious i'm using the bitmap font instead of the blackletter one.
font code try #3: successive lines are being drawn on top of each other, instead of dropped down. this is because for some reason, the lineheight is not being calculated.
font code try #4: it becomes obvious letters are being drawn upside down
font code try #5: i alter something about letter spacing...?
font code try #6: i have no idea
So this is what rewriting the font code from scratch looks like. It's almost all working properly now; there's just a slight remaining issue of a little overflow past the right margin. Everything else works fine!
So: fixing that remaining bug and then I get to work on UI stuff. bleh.
December 13th, 2009
Ugh. I am so behind schedule. This is terrible. On the up side, while recoding the font & ui stuff, I looked at the pixel calculation equations I was using with a >:( face and simplified d->height * -d->resolution / 2 + (float)yPos/d->height * d->height * d->resolution
into (d->height * d->resolution) / 2.0 - yPos * d->resolution
and then into (d->height / 2.0 - yPos) * d->resolution
So that's a nice improvement, although I doubt it'll have much effect on how fast the program runs. Then again, the panel-drawing code it was for is called many times each frame, so who knows. Still haven't gotten to the point where I can draw ui panels, much less hook them up to the input and event system so that I can get the real input loop going, finally. And I'm supposed to be half done by now. sigh.
Well, keeping at it! EVEN THOUGH THIS IS SO DEMORALIZING. Although there is a certain pleasure in comparing my old code to the new code. Everything is so much more concise; it's awesome. BUT SINCE THE ENTIRE PROGRAM DOES NOTHING, IT IS STILL A FAILURE.
Probably the lesson I ought to take from this is that I ought to stop recoding everything from scratch, but... well, hopefully this code is finally self-contained enough to not require a total recode every time I change one part of it.
The panel code is as terrible as ever, though. Left screenshot is an in-progress screen, when I wasn't drawing the panel behind it properly. Right screenshot is, sadly, the 'completed' code. There's no way to have repeating sprites with the way the code is right now, since each sprite is a small cutout of a much larger texture. So instead, I'm stretching out the sprites to cover the distance, with the hope of making it look pretty later on.
Of course, before I can move on to the real interaction code, I have to code up all of the "choice" box code, which involves messing around with the text structures a little. Sigh. But after that... well, after that it's back to maps. again.
Three bugs fixed:
- When calculating the width of a space, I had typed
letter.w / d->resolution
when it ought to have beenletter.w * d->resolution
. That was the cause of the slight overrun of the right margins. - In the sprite-loading state machine, a rarely-reached portion of the code read
sprite->w = pos.x - sprite->x
instead ofsprite->h = pos.y - sprite->y
. What resulted, because of the position of the line in the state machine, was a sprite being loaded with a usually-negative width and zero height. Because of that, when the sprite was drawn it was given an absurd width and would push any characters after it off the right edge of the screen, making it appear that the rest of it had not been drawn. However, this only appeared on characters that did not intersect with the baseline and were also above the baseline— characters like ' or ", for example. - When drawing a very long word, I had not thought to think of what would happen if a word was longer than the space available on the line, and as a consequence the code acted strangely by dropping the word a line lower with each successive letter past the right margin, until it ended. This usually pushed the line and any following it off the bottom edge of the screen, making it appear that the rest of it had not been drawn.
an additional unsolved problem: the line height calculations are incorrect and I don't have the focus to fix it right now, so lines that include a character with a negative offset (see above; characters like '"~) are shortened until they overlap partly with the line above them. The real solution to this involves establishing the baseline and line height as the sprite is loaded up, instead of storing it in the text code, because then lineheight is based on what characters show up in a given piece of text, instead of being a feature of the font itself.
But that involves working on the sprite state machine and I really do not have the focus to do that right now!
On to the events system!
December 14th, 2009
NOTES TO SELF:
- There really isn't a place to store all the data about a text fragment. Reasonably, it seems like most of it should go inside the text fragment struct itself. That'd be a good place for alignment info, at least. More complicated are the text styles (currently just text color, but hypothetically expanded to bold, italics, large or small printing, blink, etc), because there are a lot of possibilities for use in individual words, which would mean using a kind of ANSI style with an escape character and everything. Honestly, using exact ANSI code would not be a bad idea. But they would have to be read during text fragment creation and stored away in each text line to work properly, and that would cause problems when they need to be overridden, like e.g., when a menu item is changed from normal to grey colour because it's become invalid.
- Creating text boxes is an incredibly laborous task, considering that right now each part of it (each chunk of text, the panel background, each menu item) has to be created seperately and combined. I ought to code a function that takes, like, a panel x & y offset, width, and text values as strings, does all the calculation once in itself, and returns the newly created text box.
- Line height is still stored in text fragments instead of in fonts. This is definitely something that needs to be fixed.
- Setting a menu item to trigger an EVENT_CONFIRM event creates an infinite loop when triggered, because EVENT_CONFIRM triggers the event of the menu item the cursor index is over. Consequently, the hypothetical text box creation function mentioned above should scan through the menu item events and disable ones that will cause bad things to happen— offhand, things like EVENT_CONFIRM and all continuous events.
- It would not be a bad idea to have each UI panel create and store a GL display list, so that all of the calculation isn't done each frame. Old versions of the code even did this!
so far today i have fixed the input/event system that i wrote yesterday to not crash! since events are freed once they've run, putting an event referenced elsewhere onto the system event queue is a bad idea. then events that could be triggered more than once from a menu (right now, every event; in the future things like dummy events) crash everything. now they do not, because now it copies the event and enqueues a duplicate event.
but the input & events system is in place enough for me to start working on maps, because now i can set up the opening screen and add in EVENT_INITIALIZE_NEW_GAME and make that properly establish a field. which means it's finally time to test the animation system, too, because that comes up when i get into map rotation.
The most exciting screenshot ever! At least, I know the screenshots that are of 3D errors instead of panel alignment problems tend to interest me more.
Hooking the field code up to the rest of the program was more complicated than I first assumed, mostly because I completely forgot about the depth-sorting problem. Thankfully my old solution still works as it should, but importing and updating all the code took a while, because I had to write up all the camera functions, including the tricky tile depth comparison one that requires a global.
But now, finally, again, I can start working on maps! I think I'm just going to throw up an ugly cluster walk, as I mentioned in my last post about map generation and push for getting free movement and physics working. And then comes tile traps (which is my term for any events triggered by walking on a tile— map transfer, for example, also counts) and dialog and maybe a non-trivial menu system. Then I can maybe think about working on the new tile setup and map generation that's actually based on clusters.
Of course, with this update I've introduced some new (and old) bugs. I'm going to list them here so I don't forget:
- The falling-out-of-view bug is back, although not in the dramatic way portrayed in that update. I actually fixed it a year ago, by passing the camera height to the tile drawing functions. That's prohibited in this iteration of the code just because, so I have to think of a more elegant/tricksy way of doing it.
- actually, that's the only bug i can think of. i hope that doesn't mean i've already forgotten the rest.
...I love that I have links in this update pointing to September of 2007, 2008, and 2009. Progress! With an extremely limited definition of 'progress'.
December 15th, 2009
Map generation is go! No screenshots, though, because there hasn't yet been a photogenic error. All the problems so far have been animation related.
- setting two animations in a single frame causes some weirdness with some queue or another, leading to memory leaks (i assume of dropped animations). this is easily triggered by mashing the 'rotate' and 'tilt' buttons at the same time. probably has something to do with the list queue code, because it still hasn't been debugged.
- because of the way animations are handled on removal, i think there might be the possibility of them sometimes not getting to run their 'final pass' code, which can lead to bad things (like suddenly being unable to rotate the map because the camera rotation lock wasn't lifted). i need to take a look at the animation system and either have a special block for animation events that need to do closure stuff or make sure that they are all run with a final pass.
actually, there is a tile error that has some degree of visuality. right now, the tiles are drawn with tiny sub-pixel gaps between certain edges, and the effect is really distracting and ugly. bleh. i'll fix it later, once i figure out why it's happening.
as a bonus, here is a screenshot of the out-of-view bug, here caused simply by having a badly set draw distance.
also, i think this new zoom code zooms out too far.
December 16th, 2009
All I have discovered so far today is that yes, the list code is messed up and doesn't properly trigger an auto-resize when queues are full. So the animation list fills up and the first animation is overwritten, so it's never freed. Also, for some reason it looks like there are two enqueues for each animation, instead of one. hmm...
December 17th, 2009
I ended up doing almost no programming yesterday! I wrote several thousand words, though. BUT THAT IS OFFTOPIC.
I discovered some critical flaws with the input system I have not yet thought of a way to fix! The way SDL input is handled means that it's possible for a button press to do nothing if it's rapid enough, because for the most part I disregard the sdl events themselves and only check to see if the keys are pressed— this is so commands repeat if you keep the key held down. There is a SDL function to enable automatic event repeating, so that would be one way to solve this. Plus, there's a memory leak in the event queue, because of the buggy list code.
I have resolved to not fix these problems (well, if I can figure out the list one I will fix it, but...) because there is surely a simple and elegant solution to them, and if I take forever trying to think of it I will get nothing done. In the mean time, I'll just increase the event queue size and be sure to avoid briefly tapping the keys.
So I'm going to animate the cursor to test a few animation settings and then I am going to move on to DRAWING UNITS and UNIT MOVEMENT AND PHYSICS and come back to the buggy code sometime after the end of this year.
December 19th, 2009
So I've actually been working on some style stuff, mostly things that the old version had and this version doesn't. Specifically, system state debug text in the corner and a tile index.
The state text I got working no problem; all I really had to do was copy and fix some old code and then write the ALIGN_RIGHT part of the text-drawing function. The cursor... well, there were problems.
This is the first time I've actually tried to draw billboarded sprites in this iteration of the code, and the function... doesn't work right yet. Most notably, these two problems happened when I forgot to call glEnd ()
at the end of the sprite code. Well, at least it tells me that the real png transparency code works right!
That's like better! I was just applying the offsets wrong; it all draws properly now.
Now to make it so you can actually move the cursor around, as a precursor to the more complex colliding interaction of unit movement.
December 20th, 2009
These are technically from yesterday, when I was still attempting to get the cursor-drawing functions working exactly right.
When I first starting testing the cursor drawing code with proper depth placement, I realized that the code I had drew everything backwards, from closest to the camera to furthest.
Mere still shots cannot describe the space-twisting power of looking at a map that's being drawn in the wrong order. Rotation makes everything fold over on top of each other in ways Escher would be proud to see.
Also, with this iteration I had done nothing with heights, and so I hadn't added in the height offset tile drawing. I figured I ought to do so now that I had a cursor, and then this happened. I had forgotten to alter one of the tile drawing height values, so while all the corners drew relative to the cursor, the centers drew with the absolute height value. This means all the tiles turn into mounds, spikes, or hypothetically pits as the cursor offset changes.
I forgot to call glEnd ()
after the cursor code. I keep making that mistake now; I've never done it before. At least it's a new class of pretty bugs.
There are substantial problems with the cursor code, most having to do with occlusion (as you can also see in this screenshot) but others having to do with other things: the mockup code as it is now has no capacity to interact with layered tiles; there's no animation handling height changes yet, so the camera jolts are back. etc. etc.
I think I might be a little more optimistic about making progress if I didn't keep having the same problems on every single iteration of code. Oh well.
December 21st, 2009
Started seeding the random number generator. Fixed a bug in the map generation code that resulted in a crash if the 0,0 tile wasn't filled. Cursor is now height-animating properly. The map generator code has some issues that can only be solved by tracking directions tried again, which I'm not willing to do right now. Considering getting tile joins drawing before starting work on unit physics; all the code was completed in the last version so I ought to just have to copy it over. Code has no real support for multilevel tiles. Still don't know how to properly calculate the slope of hexagonal tiles, so all height testing is being done vs. the center of the tile.
The worst part of this is that this was completely intentional; I forgot what tile corner values mapped to what actual corners, so I upturned one (and downturned another) of them to check.
I also finally turned on face culling, and realized I had been drawing parts of the UI backwards the entire time.
Today mostly I worked on the mysterious tile height code, which is something I have never ever tried before in any of the previous versions. It's half completed: the cursor can accurately tell which triangle of each hex it's over, but I still don't know how to get a usable point to interpolate with, much less how to interpolate over triangles (well, I'm assuming I can do a quadratic interpolation with one argument missing, but I have not yet tested that assumption).
So I haven't done much so far! Maybe I should see if I can get the supreme basics of unit drawing done before I go to sleep.
That's supremely basic enough for me! And now to play Morrowind for an hour before I go to sleep.
December 22st, 2009
At first, unit moves with absolutely no collision interaction, so you could happily move off the map. Because there is no collision interaction, there is also no tile interaction, which means the unit is not drawn in the right depth order.
Of course, even once there is depth testing there are still occlusion problems.
I made sure to always place the unit on an existing tile if 0,0 didn't exist, but at first I didn't set the unit's position properly, and that lead to the unit off on an non-existant tile and unable to move because position, not tile location, is what was tested for movement. So then I wrote a function that set both properly and made sure to only alter unit location through that function!
blah blah blah, more occlusion problems. the cursor shouldn't even draw now. I'm going to have to fix that eventually.
SO AS IT IS RIGHT NOW, units (specifically one, player controlled unit, because I don't have the proper events set up right yet) can move around on the map, but there's no physics action-- a unit moves almost exactly like a cursor, only it can't go over tiles that don't exist. It isn't blocked by tiles higher than the one it's standing on; it completely ignores tile height provided there's a tile there at all.
So make this reasonably fully-featured, I have to chop the code I just wrote up into several discrete events, and then make user input create a EVENT_ANALOG_MOVE_UNIT event with the player-controlled character, instead of the broken way it is done now. Also, I have to write code for jumps and make that into an event the player can trigger for the player-controlled unit.
But to get jumps to do anything, I have to get the physics functions to update. And this is the very first time I've ever attempted something like this. fff, and I don't even remember the equations for gravity. Time to make something up!
Also on the to-do list is properly updating a unit's facing based on its direction and properly drawing that facing based on the camera rotation. Maybe hooking up an animation to the unit, so that it has a walk animation based on how rapidly it's moving. That would require having walk sprites, though.
It's not very clear here, but he's floating in midair. Physics is something that can be done, but I have no clue what the equations of motion are. So there are no forces at work, excepting the direct movement of the controlled unit by the player. Hypothetically, I could write the jump setup code, but until the physics system knows what to do with velocity data it wouldn't do anything, either.
My 'ideal' physics system would allow for a few types of interation. I WILL LIST THEM HERE, FOR MY OWN AID:
- Most obviously, units ought to collide with tiles. This means detecting where and at what angle a unit collides with the surface or join (which still do not exist) of a tile. If the tile terrian is bouncy, this means calculating a rebound effect from the unit motion and angle, like the unit is a big rubber ball. (...most tiles will not be bouncy. I can already envision the kinetic problems with controlling units that never stop bouncing. Also, I've played Phantom Brave.)
- Hypothetically, if units go flying around the screen bouncing from tiles, it seems likely that at some point a unit will hit another unit, either stationary or also moving. Again, they should impart force and potentially rebound like rubber balls.
- Also, since units can jump, they can jump on top of other units. (the idea is for units to block out a cylander as tall and wide as their sprite.) I suspect I might get big into the idea of using real frame-of-motion physics, with slippage across moving surfaces and everything. Nothing is more annoying in video games than missing a jump because the physics system forgets that a character's velocity is the sum of the velocity of everything it's standing on. Not that I think tiles will be moving at all, or that units will move with enough speed to make a difference.
Of course, since at this point I barely even know how to integrate, well, maybe there's some other stuff I ought to focus on. Like, hey, testing out multiple moving units and tile events and maybe even MAP TRANSFER, finally.
December 23rd, 2009
So where to start...?
Integration works, kind of. I mean, it's terrible and not even doing proper physics or integration, but there is gravity and collision detection between tiles and units. That is what I call a win!
I'm not sure why the sprite is drawing so weird, but the fact that the sprite sheet looks like this might have something to do with it. I... well, I wanted to not have to worry about quality sprites and instead finally have a 'complete' sheet with multiple poses. (the best part is how they get bigger and bigger, to the point you can kind of tell which order i drew them in by how huge they are. whoops.) And that sheet does, in fact, have multiple poses. UNIT_STILL and UNIT_JUMP, in fact. I'm thinking of adding UNIT_STAGGER as a pose or a series of poses for when units get knocked around.
Unit facing changes properly as the camera is rotated, and it changes improperly when a unit moves. whatever. all issues previously mentioned still exist. wait, no, I fixed the issue with the cursor still drawing even when it shouldn't. although I never mentioned that one, but it was obvious by looking at the screenshots.
Oh, and I added shadows to units, because I discovered that it was completely impossible to tell where they were located while jumping. the continued lack of tile joins does not help.
TIME TO TEST TILE EVENTS. AND MAP TRANSFER. i swear, if i can get map transfer working i will be so happy. talk about reaching new ground.
Oh yeah, and the more I think about physics, the more I'm tempted to somehow create moving tiles. It would be great!
December 24th, 2009
The first attempt at creating autosized text panels fails.
And then the second attempt to create autosized and auto-placing text panels fails. I forget the exact error; I think half of it had to do with the text fragment creation code and half had to do with the way it was invoked by the panel code.
It's becoming increasingly obvious to me that I really, really need to write a lot of small data-fetching classes. For example, the program needs to check the dimensions of panel edges and corners all the time to draw ui panels and text on top of them, but right now the only way to get the data is to manually check the sprite sheet data. I need to write a function that will do that itself, like spritePanelBackgroundBorderDimensions ()
or something hopefully less long-winded. And then I can call that function and make everything seem simpler than it is, because complicated calculations will be hidden behind a simple function call. YES, YES, THIS IS WHAT ABSTRACTION MEANS. Yes, I fail for not doing it before. Except, well, this month is the month of writing terrible but working code. I'll fix it all in January. Or February.
Of course, there are further problems. The point of all this panel stuff is so that I can get to the point where walking over a tile can trigger an, I dunno, EVENT_OPEN_TEXT_PANEL event, which will pop up a text panel (like, say, you're close to something of interest that you can inspect). The problem I am having is that I can't think of a way to make it close once the unit walks off the tile (or in more complex cases, like if the player switches which unit they're controlling while on the tile-- in which case the event also has to recur again when they switch back, without moving) OR to make it open multiple times without running into the crashy "event is freed upon first use but it's activated multiple times" bug again, only this time with text panels. maybe I should start with reworking the way I do event variable argument lists.
bleh.
So then I rewrote the event variable argument lists. This program is utterly destroying the type system. This is not a good thing.
In more notes to self, I ought to keep in mind maybe adding an interface for manteling tiles, because I just wandered around in a map that required me to walk around in a big loop because one of the tiles was thigh high above me and that was higher than I could jump with my mediocre jump stat. But maybe that's too Vagrant Story-esque. But conversely, if there are going to be thigh-high obstacles, there ought to be manteling because I've played enough Breath of Fire 3 to have a deep burning fire in my soul in regards to stupid map design that requires you to walk miles out of your way to get over an ankle-high blockade.
Those are not correct joins.
Those are considerably worse.
It has been a long time since I've posted a wireframe shot. But it does show all the joins drawing properly! Hopefully this will help in the fight against confusing depth issues.
December 25th, 2009
The primary issue here is that in order to test dialog with units I have to get the physics system working properly (as the physics system is what would tell units if they're near other units), which would take forever. Likewise, to properly test back-and-forth field traversal I need a field generator that understands the concept of the outside border of the field, so that it can attatch the gates there. Both of those are complex tasks that I'm not quite sure how to do yet. So I suspect I might make the first field traversal test work like dungeon floors in Nippon Ichi games— there'll be a 'gate' somewhere on the field, and entering that tile should transfer you to a new field, which also has a gate somewhere.
Also on the to-do list today is setting up text-drawing animations and text panel advancement, so that I can make a huge wall of text and draw it three or four lines at a time, as is the fashion in RPGs.
I was messing around with jump height and it looks like the camera depth is still not properly set.
So aside from the placement, tile events work. There are several problems with them as of right now, though:
- They trigger instantly when the unit walks onto the tile, so in the cases where the unit is jumping or whatnot they stop abruptly. If physics worked properly, I think adding a tiny bit of inertia to unit movements would make the movement seem less weird.
- Because of the terrible unit camera tracking code (the less said about it, the better), it stops following the active unit when the system state changes to something other than STATE_UNITFREEMOVE. This is a problem if a tile event is triggered in mid-jump, because the unit continues to be affected by physics, but the camera no longer tracks their fall.
- They cannot be triggered more than once. Since the text panel is freed after the event is handled the first time, invoking the event a second time means dereferencing a pointer to freed memory, which in the best case crashes the program.
- They are so incredibly annoying to set up. here is the code required to make this one single box:
tile->event = eventCreate (
EVENT_CHOICEBOX_OPEN,
argsListCreate (1,
argValCreate (
"box",
TYPE_MEMADDR,
choicePanelAutofitCreate (
system->panel,
NULL,
system->font,
1,
system->display->width / 2,
"Do you want to move to the next map?",
2,
"Yes",
menuItemCreate (
NULL,
eventCreate (
EVENT_CHANGE_FIELDS,
NULL
),
PRINT_NORMAL
),
"No",
menuItemCreate (
NULL,
eventCreate (
EVENT_CANCEL,
NULL
),
PRINT_NORMAL
)
)
)
)
); - There's a memory leak— if the text panel is never displayed, it's never freed. On top of that, there are a few addresses somehow related to this mess that aren't freed either— probably the tile's event structure itself, but I haven't checked so I'm not completely sure.
The problem is that the tile event trigger needs to have all the data required to make the text panel, but (annoyingly) that's a lot of unrelated data— the background panel to use, the font to use, etc. What I actually need to do is work out what are the most commonly used text boxes (title screen, system menu, that kind of thing) and how their arguments vary, so I can write a function that will probably use globals so that I don't have to pass in a lot of arguments that will never vary.
Then, making most text boxes will be simpler and hopefully I can have the EVENT_CHOICEBOX_OPEN 1) changed into just EVENT_TEXTBOX_OPEN and altered to be capable of dealing with any kind of text box and 2) have it store the data to create a text box, not a pointer to an already-created text box.
The fundamental problem is that all of the functions I've written so far are pretty low-level and allow for complete customization of the structures they set up. Usually, I do not need complete customization. What I need are some simpler functions that aren't such a hassle to call. This is kind of a problem, because no matter what the form the function takes, I still have to list at some point what the menu items are, and those alone is a big chunk of the nested function call above.
So things to do:
- DO THE ABOVE. a text panel creation function that takes only width, lines on screen, and the text to display. a menu creation function that... can be done in multiple steps?
- Write a few functions for altering UI panels after they've been created— adding or removing options from choice boxes, positioning them around the screen, that kind of thing.
- Work on 'long' text boxes that have multiple 'pages' of text, and mess with their animations and events so that they look nice. Probably also merge the various text box structs together— 'text boxes', for example, can just be 'choice boxes' with zero responses. or something.
I see that this month the log has just become a big reminder note.
December 26th, 2009
Further camera positioning difficulties when I switched back to cursor control convince me to make a proper camera-tracking function, that'll make the camera follow either the cursor or the currently controlled unit, if either exist.
at first, camera positioning does not work...
...and when it does, it is revealed that the auto-sizing code does not work either.
Everything is spiraling out of control! I have been working all morning on getting the triggered-multiple-times text boxes to work right. Now, instead of storing a direct pointer to a textbox, the EVENT_INTERACTABLE_TEXTBOX_OPEN (which is the rather longwinded enum name I have decided upon) event takes a text argument and a paired responseCount/responses argument, which are used to construct the textbox.
Except to keep the data alive... well, there is a lot of pointless memory copying going on right now. The event in tile->event
is only freed when the tile is deleted (and actually it's not yet for some reason, so there's another memory leak there), but when it's triggered a copy of it (which is somewhat non-trivial to make, because it has menu items which have to be copied, and the menu items have text fragments which have to be copied, etc etc etc) is made and added to the event queue, along with another argument saying which unit triggered the event.
That event is used as an argument to textboxCreateFromEvent ()
, which returns a textbox that has yet another copy of the menu items and junk in it, because the event's copy is freed after the event is processed. which... is actually kind of dumb, because i could use those values and unset them on the event itself, and save myself some memory overhead from all of the duplication. ...BUT THAT IS BESIDE THE POINT, because even with all this copying it still crashes after being triggered once! bleh.
One problem solved: all but one address of the memory leak were caused by me not sorting the argument list after adding certain values, so that the bsearch
wouldn't find them (and free them). also, new problem: I changed the code to take the menu items directly out of the event, and now it crashes when first tries to draw the textbox.
copying menu items: from old item 0x9b04990 to new item 0x9b04990 at offset 0
. I don't know why this is happening, but yeah, that could be why there are crashing problems. also probably contributing: the loop that copied all of the menu items didn't catch the last one.
phantomnation: event.c:89: eventCopy: Assertion `oldItems != newItems' failed.
oh.
copying event: from old event 0x842f4a8 to new event 0x8430608. copying menu items: from old item 0x842f7d8 to new item 0x84308c0 at offset 1
copying event: from old event 0x842f4a8 to new event 0x8430608. copying menu items: from old item 0x842f658 to new item 0x8430a40 at offset 0
creating a textbox (0x842fc58) from event (0x8430608). using event's menu items: just got menu item 0x842f658 at offset 0.
creating a textbox (0x842fc58) from event (0x8430608). using event's menu items: just got menu item 0x842f7d8 at offset 1.
phantomnation: ui.c:21: menuItemDestroy: Assertion `item != ((void *)0)' failed.
So while there were many bugs involved in the code, and there were in fact many reasons why it crashed (or would have crashed had I not fixed them before they triggered), it seems the primary problem was that because I was taking the menu item pointers from the event and setting them to NULL, the event when freed still tried to free those NULL pointers. I changed the event free loop to only try to free the pointers when they're not null, and I added an assert in menuItemDestroy ()
to make it more obvious what is happening in the future.
There's still a memory leak, though— there are eight bytes never freed from somewhere, and an additional eight bytes being lost every time the text panel event is triggered. Ugh. and because I'm copying memory all over the place, I have no idea where it's getting lost. This is why you shouldn't do what I'm doing! [like, two minutes later: wait, no, it was the two menu item pointers in the event structure; i had gone to such great lengths to make sure the menu items were freed but paid no attention to the memory their pointers were taking up.]
Also, sometimes the map generator makes a field that is... less than robust. All of the maps so far in this iteration have used an algorithm that mimics the very old map generator from the first iteration of the code, with a few differences most because the old version did better attempted path tracking. (and in fact, the old version does better attempted path tracking because i ended up with a few maps like this one and made it not do it anymore.) Because this version doesn't, it tends to get a little more spready and forked than the other version. ...also this version does no sloping whatsoever. I ought to work on that, too.
Anyway, so panel text triggers work perfectly, or at least as well as can be expected with the rather lackluster panel code I have right now. NEXT STEPS:
- actually make the "Yes" option transfer you between maps
- make EVENT_INTERACTABLE_TEXTBOX_OPEN and EVENT_FLOATING_TEXTBOX_OPEN be more generally useful
- the previously-mentioned long textboxes that don't display all their text all at once, and the animations required to make them look good.
And map transfer works! Moving onto the gate tile will pop up the textbox with the choice dialog on it, and choosing 'yes' will immediately transfer the unit that triggered it to a new map. The transition itself is kind of terrible: the unit is centered on their new tile and flat on the surface of it, so that if the transfer is triggered in midjump they abruptly land and also move to the center of the tile. Right now it's not a big deal, but in the future when units can walk back and forth between two maps it'll look awkward.
The system is basically straight out of Item World, too— you can only go further, never back. This is also a problem, because in the future when maps are not just fields of staggered flat blue circle hexagons we will want to be capable of walking around, not just going from featureless random field to featureless random field, which means EVENT_CHANGE_FIELD needs to take some arguments about which field to generate. That'll be a big part of writing the real field generator and the scenario system, I think, getting all the arguments down for field types and stored seeds and whatnot, so that the game can regenerate a field from a given seed + saved field data perfectly.
Whatever. That'll happen later. Right now, it is time for... well, actually it is time for me to get off the computer and go grocery shopping so I can make food and not starve to death, but after that it is time to work on fixing up the ui system.
Well, maybe it's a little too early to declare the field transfer code working right.
December 27th, 2009
I already knew it, but it's kind of humiliating to see in practice how terrible your "pick a random tile" code is.
Blah blah blah, there is some attempt at 'collision detection', even though the physics system completely ignores all collisions. Basically, the physics system checks the tiles around each unit (but I just realized, not the tile the unit itself is on!) and builds a big list of how far units are from one another. This is completely useless and not at all what the physics system needs to calculcate collisions, but it is something I can use to detect if units are close together to trigger non-physics events like popping up their mini status display, which would denote that you're close enough to the unit to talk to it. This is taken totally from Nippon Ichi games!
Of course, even when I have it set up and working (I haven't even gotten far enough to test it, really) there's still the formidable problem of figuring out just how to trigger the events. It's very easy to say "and when the controlled unit gets close to other units, the other unit's display pops up and this means that pressing the CONFIRM key will trigger dialog with them" but working out where the code to do that ought to go without breaking everything (or undermining the structure of the system) is somewhat harder.
Also, the depth-testing for units on tiles still needs some work. The unit movement function, actually, is really lackluster. I think what I ought to do is have any code that messes with unit position immediately call, like, unitUpdateTiles ()
or something, so that one function can handle unit movement along a tile (if the unit has traversed a tile boundary, update the unit's tile position by removing its occupancy on its old tile and adding its new occupancy on its new tile and also trigger the tile event of the new tile, if it exists. if there are other occupants of the tile, update the depth sort list. if the tile is sloped, update unit height. etc etc etc). I realize this is what I said I did a while back, but oh, how this current setup does not do that. But I'm too tired and whiny to do it tonight, so... I won't! THIS IS THE KIND OF DEVOTION I GIVE TO THIS PROJECT. sigh.
Evidently not quite tired and whiny enough to not fix the unit movement code, though.
There was a long, complicated bug that happened because while coding I realized what I really needed was a listOffset ()
function1, the inverse of the listIndex ()
function— with one, you give a list pointer and an index and get whatever is stored at that point in the list. The other, you give a list pointer and an arbitrary bunch of bits, and it returns the index of the first occurance of the bit string as a list item2. if it can't find the item, it'll return -1.
For a while, it would always return -1 because I didn't write the memcmp line properly! And I didn't test for that, so that down the line when (in this instance) units tried to get the -1th offset on their tile lists the game would crash.
Sadly, units still look terrible when they walk over each other. Part of this just has to do with the fact that units can walk over each other, so most of the problem will go away once they are actually blocking space. The other part of it has to do with the depth distance calculations being done against the sprite's real position coordinates instead of offset (somehow) by their sprite offsets. This manifests in sprites stepping on each other's feet when they get close. But, like I said above, when units block they will not be able to get close enough to each other to manifest the problem.
1 along with a multitude of other functions, any of which might somehow be causing the problem. Among them, just to maybe get a concept of how I am thinking about this:
tilesFindFloor (list, x, y, z)
- ...which takes a list of tiles (assumed to all be on the same x, y coordinate) and returns the highest tile still lower than the given position, or
NULL
if all tiles are higher. thex
&y
are needed in the case of sloping tiles, which are still not fully implemented. In practice, they're not required— tiles on the same coordinate are prevented from intersecting each other, so really if a z position is between the lowest point on a tile and the highest it's safe to say it can be placed on that tile. tileAddOccupant (tile, occupant)
- ...which takes a tile and a somewhat arbitrary occupant structure (which can be a unit or a cursor right now, with a '3d model' entry that doesn't do anything and is never used so far) and adds it to the tile's occupants list. Then it depth sorts the tile's occupants list, if it needs to.
tileRemoveOccupant (tile, occupant)
- ...which is the inverse of the above. it removes the given occupants and sorts the list, if needed. returns FALSE if the occupant is not actually in the tile's occupants list.
unitMove ()
into two functions— one to calculate the new position (with a proper amount of leeway, sliding around obstacles), and the other to handle the movement itself.
[↑]
2 note how it works with BIT STRINGS, because I have demolished the type system that thouroughly. not good. thankfully lists take only a single type of data, or else I'd have crazy mad problems with things like discerning NULL (otherwise known as (void *)0) from 0 down the line. [↑]
December 29th, 2009
At first, I started by working on text line drawing animations. The idea is that textboxes will keep track of what lines have been drawn and what line (if any) is being drawn right now, and kind of draw it vertically into place as the timed animation it's attached to completes.
It does not work right yet.
Mostly, this is because I can never really quite get how sprite offsets work into my head completely enough to work out how to do the proper calculations (and what still-missing values I need to do them). It is complicated! And so while what I need to do is just sit down and write out diagrams and do tests, um, that's not what I did.
I did, at least, realize that it's very easy to correctly draw part of a letter, even through the cutoff points are not aligned across letters. So that's (hypothetically) what the draw animation would do right now. But it is also not written yet!
Mostly I got textbox support coded up so that 1) textboxes that don't have responses don't crash the game and 2) textboxes draw line-by-line in something like the manner they ought to.
It's kind of sad, but it's a little compelling to wander around the little maps and trigger the textboxes scattered around and head to the next level again and again and again, even though there's nothing to actually do. Which was eventually brought to a halt when I generated a map where one of the text box triggers had written over the next level trigger. So I fixed that as well (by making a tileIsBoring ()
function that checks if a tile has an occupant or an event already) and then continued wandering around.
Welllll, I am pretty sure that I am not going to get the battle skeleton done in the next two days, so that will technically mean I have failed my goal for the month. ...but there is still time! I guess. Plus there's the code from the older version where I got all that working right. ...but that would also require coding up all the pathing functions for this version, as well.
I think I might just stick to getting wandering around more complete. fff, battles, who needs them? Maybe I could get sloping and tile heights working for the first time ever, or read up enough to make the physics actually integrate. WHO KNOWS WHAT THE FUTURE HOLDS?
January 2nd, 2010
My first attempt at physics integration ends disasterously! Since I actually know no physics, mostly I am going by what one awkwardly phrased tutorial on the internet says. Well, whatever, I got something wrong. Something to do with accidentally using a unit's position as its velocity, but since I have no clue what the equations mean I can't really fix it properly.
At first, jumping made the unit fling itself ever faster away from the 0,0 tile. I have fixed it to the point where instead jumping makes the unit ascend slowly forever. (because their momentum is recalculated from scratch every physics step, without ever referencing to their old momemtum. or something.)
I cracked open the physics textbook I bought at the library a few months ago when I saw this on the horizon, and... well, I'm sure if I understood any of it, it would be a great help. Soooo it looks like "read and comprehend the first several chapters of this general physics textbook" is on the to-do list.
I ought to get practical textboxes working first, anyway. I kind of gave up on everything a few days before the end of the month. bleh. I also ought to fix the infinite loop problem with map generation— if it creates one of those two-tile fields, trying to add the player controlled unit, a generic unit, the next map gate, and two textbox triggers all to different tiles will create an infinite loop. But that would require working on the map generator!
Also I think I might have worked out a way to do tile height calculations, finally. More details if/when I code it up.
January 16th, 2010
So I ought to stress upon you that the ordering of the tile corner height values was arbitrary. In the tile structure, there's a height[7]
value. The first six are the corners of the hexagon, in any order so long as they're contigious, and the seventh value is the center height. In previous versions I had a enum of directions that also corresponded to the tile corner values, but really it does not matter as long as everything matches.
Not so is it that way any more! The tile height resolution code can be coded most efficently when the height values are stored in a way that maps easily to the results of an atan2 ()
call. Given an x, y offset from the center of the tile, atan2 will return a value in radians, and ((int)floor(radians / (M_PI / 3.0)) + 3) % 6
matches the sector of the hex that the x, y offset is over.
Of course, when I first changed this I didn't alter the code that returned tile coordinate offsets given those directions, so tile joins (which require that function to check which edges match up with which neighboring tiles) drew on the wrong sides.
And then the height interpolation part didn't work right at all, anyway. At first it was because I was not using the right formula (the one mentioned above) and instead was doing something completely wrong with fmod ()
or implicit float-to-int conversion. And then, of course, I realized that in addition to those problems I was also not multiplying the height by the coordinate multiplier (one "tile unit" in height equals fifteen actual GL coordinate units, because the radius of the hexagon is thirty actual GL coordinate units) and so the height returned was always close to zero.
It's still not working right even after fixing those problems, though, because there are further details w.r.t. triangular interpolation I don't understand yet. Oh well. Substantial progress! in a sense.