Sunday, October 25, 2009

Have some Class, man

So I've been thinking more recently about game design rather than game implementation, which I suppose is a pretty good direction for things to take.

The next step is to make the combat system, and there's several ways I can start this.
  1. Program the "attack" command; add "if enemy in tile you're stepping on, don't move, but attack", program the attack routine. Right now I'm thinking damage should look something like C*(A*ATK-B*DEF)^2 +- 30%, which is obviously not set in stone.
  2. Program a "messages" subwindow to display "[Player] hit [Enemy]. [Enemy] missed [Player]" stuffs. Pretty straightforward, the trickiest bit is going to be knowing when to stop scaling the window that displays across the top of the screen. Actually it'd be pretty straightforward with a fixed-width font; when adding characters to a Message string, keep track of the length on the current line, when length > number_of_characters_that_can_fit_on_a_line ((screen_width - status pane width)/pixels_per_character), back up until a space is found, change the space to a newline, keep going until a fixed number of lines, prompt the player for more. The most annoying bit then is going to be handling the "more" input, but I'll just make it loop until the player presses something for now.
  3. Add enemy "AI". At the moment I'm going to start off with a simple "always move towards the player" method which will generally keep enemies stuck in rooms, but occasionally allow them to come into a room from some adjoining hallway. It's also simpler than "if in sight of player move towards" both in terms of implementation and computational complexity (no need to scan what each enemy can see), so that's pretty cool. Also would need to add an "attack" check to enemies, but that's trivial enough to keep this nondependent on 1.
  4. Leveling a character up.
#4 is the sexiest-looking, so I'm wanting to do it first. It also has a bunch to do with one of the real features of the game, the class system. And being able to make characters of multiple classes.
The player will start being able to choose from one of three to five basic classes like Fighter, Mage, Thief, and with each level up, the player chooses which class to put the level in. After meeting certain prerequisites, the player will be able to choose from new, more powerful classes with better stats and/or different skills, i.e. Knight, Ninja, Wizard, Necromancer, Ranger, and so forth. There's three ways I'd like to go about this:
  1. New characters start choosing from base classes; unlock new classes as they level up, and may add levels in any class they have unlocked (allowing for Figher3/Thief2/Mage2 characters)
  2. New characters start choosing from base classes; unlock new classes as they level up; may change into other classes a la Final Fantasy V, keeping levels. Class changes might be allowed in lieu of leveling up, but more likely will be allowed in safe locations. Or in the middle of a nest of spiders, but as it would take several hours it wouldn't be the most tactically-sound idea
  3. New characters start choosing from classes unlocked in previous playthroughs, may add levels in anything ever unlocked
Because I'm weak, and can't make up my mind decisively about which'd be best, I'm thinking I'll have three different game modes. #1 will be Normal, #2 Alternate, #3 a sort of PowerMode just-for-fun type thing. Naturally, they'll all have their own high-score list, as converting the difficulty between them would be difficult and probably meaningless, or just straight up wrong.
In order to make things work across modes, I've thought of some (of what I feel are) interesting mechanics.
When you gain a level in one class, you get behind-the-scenes points in several categories. For example, instead of needing to be Fighter5 in order to access the Knight class, you need 5 points in the Warrior sphere, and every time you level Fighter you gain 1 point in it. This allows multi-class upper classes (like a Spellsword, Hunter, or Bard) in Mode2 (where you have no more than one class at a time), and greatly streamlines the unlocking of new classes (why can't a 200th level Fighter be a Paladin when a Fighter5/Knight10 can?).
Gaining new spells is something I'd like to have happen at each level. To this end, something similar to the class progression will be used. When you gain a level in a class, you gain percentage points in one or more skill disciplines, and when those points exceed a certain threshold, you can choose a new skill or two. For example, a Wizard would gain 115 Black Magic and 45 White Magic, where a Priest would recieve 150 White Magic. As you add skills in each discipline, the threshold increases and the number of skills available would increase (so newbie Mages can't pick ApocalypseIV as their level 1 spell, but could after mastering 24 other spells). I like the notion of picking your spells at regular intervals because it relies less on random chance than finding spellbooks. Never finding attack spells for a character would doubtlessly be intensely frustrating to most aspiring Wizards.

Wednesday, October 21, 2009

Vision - II

More adventures with keeping track of where things are.

Redid LOS algorithm with doubles instead of ints, which doesn't actually affect much of anything, other than where I do my casting to make the compiler stop warning me about it. Ah well, it only took five minutes.

My other hilarious problem came about when I got into circular definition funtimes.
I want the Character class to be aware of the Map class, so it can have a pointer to the Map it's on, so it can get Tiles on the Map as part of its visibility subroutine. Fine, I declare Characters after Tiles.
I want the Tile class to be aware of the Character class so it can have a pointer to the Character standing on the Tile, so it can tell Characters wanting to move onto the Tile whether the position is occupied or otherwise. Doable, just declare the existence of the Character class before implementing the class; as long as I only need pointers to it, it works out okay.
I also want to draw the single topmost object on the Tile, in the order of Character, Item, floor. This lets me cut down on managing drawing the player, drawing the tile, drawing enemies if they're in sight etc. and just draw what's on any given tile. This also keeps me from drawing several tiles over another, which is ugly because the non-colored space on each letter is transparent, so you can see the floor through the player. (Why not leave the black background on each Image and just draw Tiles then Items then Characters? Because then the 3D engine would look odd, silly)
PROBLEM: the draw() routine for the Character class has yet to be defined, seeing as we've yet to implement it, having only declared its existence. Can't I just declare the Character class before the Tile class? No, because then I'd run into similar issues within Character's implementation, only many more times over, seeing as how heavily several functions in Character rely on the Map class, which has a 2D array (vector) of Tiles, and Map also uses Tiles' functions. [From here on out, when I say array, assume I mean vector. I don't explicity say vector because of the Math and Physics associations they have, and I rather like those things too]
So, not too horrible, as it's only one function, and it's a function that most of the classes so far have, along with a member of the Image class (which holds several SDL_Surface*s and deals with scaling and recoloring them, very handy). So I figure, I can inherit both of those from a new base class that just has an Image and a basic draw() routine, and use virtual functions to handle the Images differently depending on the class. Turns out that you can't declare a class's base class without also implementing the class; so I was back where I started. Then an ingenious idea hit, use polymorphism. This would've been fantastic, if Visual Studio didn't forget how to use virtual functions for no apparent reason (either that or I'm doing it wrong but no I'm not shutup), and the base class pointer to a character didn't only use the base class draw function.
So, in an act of spaghettification that I wasn't particularly looking forward to, I declared the Tile::draw() function and then implemented it after the Character implementation. In the separate character.h file. Oh goody.

On further reflection (i.e., typing this entry, a.k.a. the entire reason I'm writing this down in the first place), the polymorphic approach should work, and I can't think of why it doesn't. Trying it again - I first re-read my references and run the debugger at the point at which I call the polymorphic draw() - I try making the base class' virtual draw() a fully virtual function, so it doesn't ever to execute. Turns out that it uses the base draw, even though the Character class redefines draw and has a virtual pointer to it, because the redefinition takes place after the point at which the base->draw() call occurs. So in short, it's an extension of trying to use Characters' draw function without declaring it, only without the compiler errors.
Has no one else needed to do something like this? Oh well, back to the spaghetti. On the plus side, ugly code is better than code that doesn't work, so at least I've got that going for me...

So what did all that have to do with vision?
Not altogether too much, but it does let me do some cool things. For starters, differentiating between visible or out-of-sight tiles is doable without needing to store visibility data on the tiles themselves, which would be useless data most of the time, given the probably small percentage of tiles on a given Map that would be visible at any given time. Secondly, it removes the need to keep track of what has been drawn on what tiles already; it's implicitly assumed that each tile is only drawn once (and would draw the same thing each time it's drawn). Thirdly, keeping track of which enemy is on a given tile greatly simplifies finding which enemy you just attacked, or which ally you just healed, etc.

Tuesday, October 20, 2009

Vision - I

Finally got a Line of Sight (LOS) algorithm implemented.

My first quick-and-dirty "ingenious" idea that struck at around 11:00 at night (PROTIP: you should never code when you're tired, even though I'm going to break this tip (again) as soon as this post is done) was to use a filling algorithm in the vein of breadth-first-search, i.e. starting from the tile the Character is on, giving each tile considered a value of the current tile's value+1 and pushing on a priority queue until the current value was greater than the player's LOS distance. Then I realized that I'd need to increase the value by sqrt(2) for diagonal values, unless I wanted the player to be able to see in a square.
It wasn't until AFTER I'd finished coding it that I realized that this would let the player's sight "spill" around walls. By this point, it was nearly midnight, and 8am class was beginning to rear its ugly head at me from the future, so I put my code, my computer, myself to sleep.

My next idea (a.k.a the right way how to do it) was to get a series of points in a circle around the player at a distance R, then draw lines from the player to those points, stopping if a wall interuppted a line. Take all the points a line went through and store them in an array (STL vector, naturally) associated with a Character. Thus we have all the Tiles that any given Character can see, and from there we can either show the player visible tiles, or see if an enemy can see the player.

As an aside, I'm wondering if there's a better way to show the duller tiles that are out of the active visibility field than storing a second surface for each image color multiplying by a down-factor (20% darker?), or storing a second surface at a slightly smaller size. For now I'm going to implement the second image with a color multiply, but I'm thinking out loud here. And scowling to myself, knowing "there's gotta be a better way".

On LOS: my first instinct was to repurpose code I'd written earlier for a different project that would draw lines circles with pixels, changing the "putPixel" faux-routines I'd written into adding Tiles to a vector and returning it (I didn't have a putPixel function written, but that was thanks to the horrendous overhead of the thousands of function calls it wound up making; something the inline command failed to help with at all). This, of course, led to several issues.
Firstly, the lines would start inside of walls sometimes, and not draw in certain directions others, but only in some positions. Quickly, I realized it was because the particular implementation of the Bresenham algorithm I was using didn't necessarily draw lines from the point 1 to point 2, so I had to rework it a bit so it would work without swapping the points.
Still, though, the visible field it gave me was completely off. The line-drawing algorithm worked perfectly sometimes, so that probably wasn't it. It turned out to be that in my reimplementation of the circle-drawing algorithm, I hadn't referred to the code that actually produced a working circle, but instead some notes on circle geometry that hadn't worked out so well at all (but looked convincing on paper). How I was doing it was dividing the circle into 1-tile-high slices from j = y - R to j = y + R and using the points on the left and right edges of each slice. The problem was, I was taking the width of each slice as w = 2*R*sin(acos((R-j)/R)) which anyone can see will cause problems. For those who actually care, the correct width was 2*sqrt(R^2-(R-j)^2).
That worked much better, and gave me a contained field in the shape of a circle that the player could see and moved consistently with the player. All well and good, but it had several holes in it near the y-axis for no apparent reason. Then it clicked that while the slice-based algorithm worked acceptably well for drawing full circles with rectangles, it only considered one point per horizontal line, which left noticable gaps near the top parts of the circle, where the slope of the tangent was much flatter.
So I buckled down and looked up the "Bresenham" circle-drawing algorithm (not actually designed by Bresenham, but similar enough to his line-drawing algorithm that people give him credit for it). The version that I shamelessly stole off Wikipedia is fairly inelegant to my eyes (I much prefer minimizing lines of code; ironic then that I'm writing a roguelike), but I so far cannot be bothered cleaning it up with for loops instead of eight explicit putpixel functions (on reflection, an idea for which has just struck; it also involves bitwise operations, which are pretty as far as I'm concerned). For LOC, it works significantly better, and gives a mostly-full circle (dropping, amusingly, only the 45-degree angles).

Now I'm off to improve it using floating-point values for x,y offsets of the points on the circle, which I'd put off until now because I'd been reimplementing the way Tiles displayed themselves and their contents. I.e., I finally started giving Tiles contents.