CariElf CariElf

New movement code

New movement code

We actually made a fairly major change to the code this week so I thought that I'd actually write a dev journal about it: CodeCritter and I gutted the movement code and re-implemented it.  For the developers out there, that meant creating a branch which generally makes me a bit nervous because I always worry that merging the branch back into the trunk is going to go poorly. But making a branch was essential in case it took us longer than a week to get the code to the point where we could subject others to it.

I'd actually implemented code over a month ago that prevented a new turn from happening until all of the units (including AI units) were finished moving, but it took forever even for the very first turn when there weren't a lot of units.  I did some investigating, and eventually found out that it was just that the movement code was really inefficient.  It was moving all units pixel by pixel, even the ones that were offscreen or under the fog of war.  Besides taking longer, this meant that it was hard to catch targeted units because they were moving at the same time as their pursuers.  It also didn't check to see if a tile was blocked before a unit moved into it, so if you decided not to attack another unit, you had to be moved out of the tile.

Now, the developer who coded this is not a bad coder; he's actually one of our best developers. But this code was written in the VERY early stages of engine development, probably before we started working on Twilight of the Arnor, and certainly before we even had the concept of game turns in the engine.  Sometimes you have to get code roughed in and move on to other stuff before you can come back to it, particularly when your betas are more like alphas.  Also, it wasn't really bad code.  It mostly just wasn't scalable.

At the time, I couldn't start work on fixing the movement code because I had probably 5 other critical things to work on and I knew that it would take at least a week to fix the movement code.  So I had to comment out my code that made the new turn code wait for all the units to start moving, and put in code that just checked for the local player to be done moving.

So while I was working on other stuff, I kept the movement code on the backburner of my mind.  It was probably actually a good thing that I had other stuff to work on because you have to do a lot of thinking before you can start coding something this critical.  I'd done the moving code for all of the GalCiv titles (1 and 2) so this wasn't new for me, but frankly the movement code for the GalCiv games sucked.  Those of you who played any of the GalCiv games probably remember the stuck turn button bug.  The main problem with the movement code in the GalCiv games, especially by the end of Twilight of the Arnor, was that it was too complicated.  So I needed to come up with a design that was fast and simple and scaleable.  I actually came up with the design while I was getting ready for work one morning, proving yet again that the best design is done away from a desk. 

In GalCiv2, ships that were off-screen or under Fog of War were teleported from tile to tile until they ran out of moves or became visible.  This was definitely one thing that I wanted to do in Elemental, but it had always bothered me that there were 3 different movement functions in GalCiv2: Move, QuickMove, and Teleport.  (QuickMove actualy called Teleport), and there were 6 different collision detection modes: pathfinding, move check (before moving into a tile), move, (on moving into a tile), quickmove (when calling QuickMove, was kind of a combination of move check and move) and teleport (used by Teleport and was just a check to see if you could teleport or if you need to fall back into moving pixel by pixel).  That was way too complicated, and every time we made a change to the moving code, we had to change it in 3 locations. 

My idea was simple but elegant: What if all the units used the same movement code and then if they were on-screen, have the graphics animate them moving, otherwise just teleport them?  I bounced the idea off of CodeCritter (who is our graphics engine guru), and he thought that it would work and agreed to take care of the graphics code side of the problem.

The first thing that I did when I started working on the new movement code was re-enable my code to force the turns to wait for all the units to stop moving.    That way, I'd be able to tell if my code was doing what it was supposed to do: going faster and not making the turn button get stuck. 

Next, I created a static variable in the base mobile object class, g_bQuickMoveAlways.  If true, it would just move the units from tile to tile (rather than pixel to pixel) whether or not they were visible.  This will be a good option for multiplayer, and it made it easy for me to test. 

Then I just had to look at the existing moving code.  I copied the existing function, renamed it, and started stripping out anything that had to do with graphics or animation.  I also broke the code that actually handled moving into a tile into its own function, to make it really clean.  All I had left to do was make sure that the unit actually did a collision detection before moving into the tile.

This was actually the most time-intensive part of the operation for me.  I had to go through all the different kinds of objects (units, goodie huts, improvements, etc) and make their hit detection functions handle two modes, MoveCheck and Move instead of just Move.  For most of the objects, this was fairly simple, but the units have to check to see if they're going to attack.

At this point, I realized that I was going to have to work on more than just the movement code.  We'd been planning on making it so that you have to be at war with the owner of another unit before attacking it, but that required interface code we didn't have, and player relations code that we didn't have, so units could attack at will.  I had two choices: hack something in so that it would work and change it later, or start laying the groundwork for the real code.  I decided that it wouldn't take me that much longer to write code that wasn't a hack, and it would save me the trouble of having to rip out the quick hack later.

There was rudimentary player relations code in that was based on GalCiv2's relations code, but it was missing some key concepts.  It didn't have checks to prevent your relations from improving from being at war to being merely hostile.  It didn't have checks to prevent your relations from just dropping into war, instead of requiring the player to declare it.  It didn't have a concept of being permanently at war, which we used for the pirates and Dread Lords in GalCiv2, and space monsters in GalCiv1.  I added all the necessary checks, made some wrapper functions for checking to see if you were at war with another player or if you were allied with them, and went to work on the existing DeclareWarOnPlayer function.

The DeclareWarOnPlayer function was just setting your relations to being at war, and it was only doing it on one side.  So you could declare war on the AI, but they wouldn't be at war with you.  It also wasn't moving all enemy units out of the territory.  So I made the war declaration mutual (easy) and started working on the problem of moving all enemy units out.  The same code in GalCiv2 was very simple, it just looked for the first tile out of enemy territory, which might be 1 tile away.  This was pretty cheesy and ineffective at preventing sneak attacks.  So in Elemental, I send the units to the nearest city.  However, what if you have no cities?  While I figured that this probably wouldn't happen very often, I had to account for it. 

Luckily, each player keeps a list of all objects that they know about, including forests and mountain ranges.  So all I had to do was go through the list and move them to one that wasn't on a tile owned by the enemy player (or someone you're not at war with). 

My next problem was that I had no interface for declaring war.  So I made it so that if you right click on a unit, it would pop up the prompt that asks you if you want to declare war on that unit's player. It's kinda lame, but it gets the job done and even after we get the interface in, it might save you some clicks.

So now that you had to be at war before attacking another player, that made it much easier to finish the unit movement code.  If a unit tried to move into a tile with another player's unit that was not its destination, it would be blocked and have to re-calculate its path.  If the tile was its destination, it would bring up the declare war prompt. 

If the unit passes its collision detection check for moving, then it moves into the tile and calls the collision detection with the mode set to Move and performs any necessary code like merging armies, collecting the goodie from the goodie hut, etc.

I was now ready to test.  I loaded up the game and started moving my sovereign, and building units. I had to tweak the code a bit as I caught bugs that made units get stuck, and the attack code needed to be tweaked a bit since attacks were now initiated before the attacker moved into the title.  Once it seemed to be working as it should be, I committed my changes to the branch and let CodeCritter start working on the graphics part of it.

While CodeCritter was changing the code to only move the unit model smoothly (as opposed to teleporting it) if it was visible, I worked on a few movement bugs that were now easier to fix after having rehauled the movement and collision detection code.  The first bug was that if you built a city and your sovereign had no moves left, he would neither move off the city tile nor be stationed within the city.  This was a quick fix, as he just wasn't being added back into the list of moveable units after being given another move to get him off the city. The second was that units leaving a city didn't do any collision detection on the tile that they landed on, so they wouldn't automatically form an army with another unit, or get the goodie from the goodie hut, etc.  Since I had my new handy MoveToTile function, I just made it use that instead of setting its position on the tile directly.

CodeCritter and I merged the branch back into the trunk Wednesday night, which mostly went smoothly.  We both nearly had heart attacks when CVS told us that we needed to update before commiting our changes, which meant that someone had checked in code after we'd started the merge.  I started shouting death threats and CodeCritter put his head in his hands, but we only had 3 minor merge errors to deal with so I didn't actually have to kill anyone. 

We're still tweaking the movement code so that the units move smoothly while on screen at all zoom levels, but we've made huge progress. Since the code is now much simpler than our movement code has ever been in any game, it should be less prone to cause bugs like the stuck turn button.

Anyway, I hope that I haven't bored all of you to tears.  I've been very excited about this new code, so I just had to share. :) 

 

 

 

 

148,382 views 56 replies
Reply #26 Top

on movement ... if an unknown entity/object causes my soldiers path to change, I would like for said unit to pause, with remaining movement points, in the location just before the change in path. This way the all annoying "misclick" which people sometimes blame for failed war campaigns can be best avoided.

Reply #27 Top



So now that you had to be at war before attacking another player, that made it much easier to finish the unit movement code.  If a unit tried to move into a tile with another player's unit that was not its destination, it would be blocked and have to re-calculate its path.  If the tile was its destination, it would bring up the declare war prompt.   

If peaceful opponent units can block movement, that means they will be able to control you without declaring war. This can be pretty exploitable against the AI if you string a bunch of weak armies across a peninsula to stop them from exploration/expansion. Ive seen this problem in a few games, where people use this to avoid the consequences of an open declaration, when it should be obvious to all that preventing movement is a blatant act of war.

In Civ4 they solved it by only making an actual enemy block movement, while units of different nations at peace can co exist. That's probably the best way to handle that.

Reply #28 Top

Quoting CariElf, reply 20

One thing that bothers me is that SD seems to be proceeding as if the current border-relations system is the final system, i.e. speding a lot of extra time and effort making sure that it works well. As one of the main ajitators for a more reasonable border system, this has me concerned.
Actually, the code that I did doesn't require the existing border system.  All I did was make it so that you have to be at war with the other player before attacking them, and than if you declare war, all of your units get moved out of that player's territory (and all of theirs get moved out of yours).  Even if we decide that you can move freely through other people's territory, we'd probably still make it so that you have to declare war and teleport the units out.  We really can't make a final decision one way or another until we get more of the diplomacy code in place.


 

 

Cari - FWIW, it'd be very nice to have a diplomatic option to allow factions to move through each others' territories.  For bonus points, it'd be cool if we could specify how much (quantity of troops, perhaps).

"Okay, in exchange for the Tome of Unlikely Power, you can move 1,000 soldiers through my land."

or

"I think you should pay me 1,000 talents of gold, and let me station as many of my troops as I want on your land.  For your own protection, of course.  You have dangerous neighbors."

Reply #29 Top

On source control, I know people bang on about it a lot, but distributed source control systems like Git and Mercurial are absolutely worth it for one good reason: local commits. This means less sitting around waiting for a central server to pull its finger out, which really adds up.

SVN is probably the simplest move though, it means less time learning the new system the tools to port your existing repository over should be pretty solid by now.

Reply #30 Top

Quoting Nights, reply 29
On source control, I know people bang on about it a lot, but distributed source control systems like Git and Mercurial are absolutely worth it for one good reason: local commits. This means less sitting around waiting for a central server to pull its finger out, which really adds up.

SVN is probably the simplest move though, it means less time learning the new system the tools to port your existing repository over should be pretty solid by now.

Agreeed - on both accounts.

I would add that apart from speed of local commits, speedy local log-operations are really nice on larger codebases with years of revisions. That alone was actually one of the key reasons that made us go for a distributed revision control system.

Reply #31 Top

Read the first half and the last part about deaththreats ^_^

 

Scoutdog and Denryu,

 

Gameplay is king and takes precedence over everything else. Don't you agree with that statement ?

Reply #32 Top

I would rather a unit encountering resistance to "stop moving" as opposed to losing their turn or auto-directing to someplace I may not want them to go "like right into a dragon", or "on the other side of a mountain range, going more backwards than forwards, in relation to the destination.

to me, penalties for running "into a brick wall" only encourage movement micromanagement. Kind-a seems prone to mis-clicks ... at least devastating in multi. At most game-breaking.

Reply #33 Top

Since the code is now much simpler...

I love simple code.  It's SOOO much easier to write and maintain.  A lot of what I've been doing at my current job is to refactor and simplify code.  Doing the same process faster and in less lines of code? Priceless! 

Reply #34 Top

Scoutdog and Denryu,



Gameplay is king and takes precedence over everything else. Don't you agree with that statement
Of course. What makes you think otherwise?

Reply #35 Top

Quoting Scoutdog, reply 34

Of course. What makes you think otherwise?

Scoutdog - That's refreshing. Of course, the auto-teleport-out system is a bit unralistic even for a fantasy game, but we'll pick this up at a later date. Nice to see that something more inventive is in the works!

 

Denryu - I'm with Scoutdog, I really do not like the "declare war get teleported out of their territory and they get teleported out of yours" option. I would much prefer something along the lines of taling a severe diplomacy hit from all factions as they note that you are a no-good sneaky bastard who attacks someone who they had right of passage. I realize they put that kind of teleport code in for a reason, but I really think there would be better ways to discourage sneak attacks on allies.

 

It may be that you want games to be as realistic as possible but one could guess that you two are ready to sacrifice some gameplay for realism.

Reply #36 Top

I am a big realism nut, true, but I firmly draw the line at ANYTHING that might even slightly hurt gameplay. There are very few ways to cover up the teleport-out, but something else like the conflict idea works just as well from my angle.

Reply #37 Top

Recently I've been playing Master of Magic and noticing the problems with its pathing system.  It was a fine game for its time, but discovering enemy units in the middle of the enchanted road (railroad, 0 MP cost) will put a spot on your shorts when you aren't expecting it. You may want to implement enchanted road system just because train wrecks are fun to watch.

If you decide you have too much time on your hands, consider implementing supply lines as a constraint on movement.  It makes Zones of Control matter and light cavalry valuable.  Everybody loves heavy cavalry, until they have to start providing wagonloads of grain to feed them.  Wizardry can, and should, counter this, even if the graphics for bottomless feedbags aren't interesting when they aren't exploding.  (Although the graphics of a burning bottomless bag corn could be amazing!  You could call it the Reddenbacher effect...) Most strategy games get logistics wrong, and that's a shame since real strategy is more about moving beans than dispensing bullets.

It looks like you have a great graphics base, and seeing what you could do with light horse archers, or even Cataphracts, would be fascinating.  The thing is that light horse archers are all about raiding caravans and other logistics chains.  When you don't have logistics, light horse is just annoying.  When you have logistics, light horse is terrifying.

Reply #38 Top

Rather than teleport out of suddently enemy territory, couldn't you just set an auto-move destination out?  That way, if the player wants to push the issue, their troops are still in the old player's territory.  It makes for more interesting diplomacy, too: "I see your troops are within our, ahem, new borders.  You see, our people need lebensraum..."

Reply #39 Top

WALL OF TEXT, oh yeah!

Glad you succeeded in merging the code :).

P.S. Why CVS and not SVN, if I may ask?

Reply #40 Top

I rather like the "teleportation out" .... because it grants those sneaky mechanics which allow you to stay within borders while attacking.

Each game has different sneaky mechanics. Sometimes its just a One-time power of a certain faction, sometimes its with following a certain guild, sometimes its with following a certain religion/mafia type.

For instance, the Svartalphar can cast "Veil of Knight" which allows an army within friendly borders to start attacking things. This works well with assasins because you can take out critical targets in one fell swoop if your ally doesn't pay attention. AKA, walk in, assasinate their hero, and leave. It also helps if you have a good number of units with you as well, to prevent them from killings you.

Reply #41 Top

Rather than teleport out of suddently enemy territory, couldn't you just set an auto-move destination out? That way, if the player wants to push the issue, their troops are still in the old player's territory. It makes for more interesting diplomacy, too: "I see your troops are within our, ahem, new borders. You see, our people need lebensraum..."

We could, but then we'd have to implement code to make them not able to divert their destination and then that complicates the code that would happen if they were attacked. 

Teleporting the affected units out of enemy territory when war is declared keeps the code simple.  Maybe we can look at changing it later if a lot of people are against it, but for now there's still a lot of core functionality that I'd like to be worrying about instead.

P.S. Why CVS and not SVN, if I may ask?

We've been using CVS since I started working here over 9 years ago and we've never really needed to change.  We've been talking about switching to SVN but we haven't set a hard date yet.

Reply #42 Top

Cari - What if you just set an auto-destination, but allowed the player to override it?  Doing so would harm relations (perhaps declare war?), but it would simultaneously remove the micro-management of making the player do it, and open up options for "what happens if I ignore this turn of events".

I guess I just don't like the game mechanic of moving other players' pieces around just by expanding my territory.  I'd rather the territory push result in a diplomatic push, rather than a physical push.

Does that make sense?

Reply #43 Top

Quoting CariElf, reply 41


Teleporting the affected units out of enemy territory when war is declared keeps the code simple.  Maybe we can look at changing it later if a lot of people are against it, but for now there's still a lot of core functionality that I'd like to be worrying about instead.

I am against it.   I assume a lot of people in this forum against it, but should I make a poll out of this??

The idea of "Conflict" feels way better, as I've described in reply#20.

Reply #44 Top

Excelent post Cari. Very informative. Keep up the Great work. :)

Reply #45 Top

Quoting Climber, reply 43

Quoting CariElf, reply 41

Teleporting the affected units out of enemy territory when war is declared keeps the code simple.  Maybe we can look at changing it later if a lot of people are against it, but for now there's still a lot of core functionality that I'd like to be worrying about instead.

I am against it.   I assume a lot of people in this forum against it, but should I make a poll out of this??

The idea of "Conflict" feels way better, as I've described in reply#20.

Well, for what it's worth, I'm strongly against your Conflict proposal that makes blitzkriegs more or less impossible. Teleporting out of the way is unrealistic and unfun, but largely preferable to being unable to conquer a city for any amount of time in my opinion.

Teleportation out addresses an AI issue:  Don't trust people who send troops into your territory. If someone has a right of passage and he wants to stab you in the back, he should be able to in my opinion. The only reason it's there in most games it's in is to avoid the ai to be alpha striked to death by a previously trusted (human) ally. Make the AI clever and it's going to say "We agree to a right of passage, but don't send your mighty army nearer our capital or we're going to be really angry". Then you don't need conflicts or teleportations.

Reply #46 Top

It all depends on how "conflict" would occur as well. The point is to not allow total destruction of an opponent on a sneak attack. If you were able to completely disrupt your opponent's ability to wage war on a sneak attack, when you would not have been able to do so otherwise, it is an anarchistic system. Therefore I would suggest that troops garrisoned in Cities should also be unabled to be attacked (other than simply disallowing a "city capture" to also disable attacking a city at all, in any way). This could represent a government's unwillingness to target the enemy civilian population before full-scale war is declared. (Only assasins could target troops within cities, and even then only very specified targets (you tell the assasin the parameters under which he selects his target, and then he goes after the weakest soldier/unit that fits the parameters, unless you specifically directed in your parameters to targer the strongest within applicable group) and even then an assasin cannot target the last troop remaining in a city. (so you can leave one person/unit in the city, and field your army, yet risk losing your army without support from that person or unit).

If however, you could use "conflict" to Von Claussevitz' type of destroying their main forces with your main forces, at any time during the game and under no special extenuating circumstances, then you will see a largely anarchistic world.

Usually, people can attack at any time, therefore its largely an anarchy where anyone can declare war at any time. However, with certain restrictions in place there can be at least a some-what sense of order. Civilized peoples were rarely in times of complete anarchy where war was constantly re-declared. To semi-represent this trend, a restriction was placed in Civilization that required most trade deals to be followed by 10 turns of mandatory peace. Im not saying that abritrary restrictions have to be in place in order to have a playable game ... but it makes for a game with better gameplay, to not allow cheap exploits which "cheapen" the experience of playing the game.

Reply #47 Top

The point is to not allow total destruction of an opponent on a sneak attack.

Well, I want to be able to do that. If a player can see your units and lets them near all its cities when you pretend you're sending them all to someplace and obviously your units aren't going there, then he deserves what's going to happen.

If the ai can't be coded to avoid that, then sure take counter measures, but don't have them prevent simple things like attacking the city on your border until the opponent had 5 turns to bring in reinforcements.

Also, I'd rather have the sneak attack a possibility with drawbacks rather than just denied. For instance declaring war with someone with whom you have a right of passage would cost you a lot of prestige. Cancelling a right of passage when you have units in their territories would cost a lot of prestige. As a result, people wouldn't flock to yoru cities and would likely leave them (you're an untrustworthy tyran after all), and other nations would see you for the untrustworthy traitor you are and never agree to rights of passage with you again.

 

Being unable to declare war for a time after a treaty is totally different in my book and perfectly acceptable. Probably even needed for the ai in most cases.

As for anarchistic periods, about all of Roman history except for a century or two were rife with civil wars and factions fighting each others, kingdoms declaring war as soon as the previous king died, etc.

Reply #48 Top

I am fine with the 'port 'em out mechanic for now. I'm actually of the hopes that the AI will allow this code to be removed at a later date.  "Yes, we have a right of passage,  but you moving that mammoth army towards my capital is totally unacceptable."  Even better if the AI does it in  some sort of United Nations meeting.  Severe diplomatic drawbacks to violating treaties should be doable with the experience stardock has been getting with AI's.  The biggest one would be that no one would sign treaties with you.  It is the reason you don't do things like this (at least not and get caught) in the real world.  Fear of a permanent and massive backlash by the other AI's and even players would prevent you from forming treaties in the future, and probably throw you into a permanent war with several other factions at the request of the offended party.

 

This leaves only the late game when it is just you and a couple opponents, or maybe even just one.  At this point the AI should just flat out refuse right of passages for troops.  Because the AI should know that there would be no real reprocusions to violating your word.  

 

Porting all of a particular factions troops out of  your land if they attempt hostile actions against you would make a GREAT defensive spell.

Reply #49 Top

I suppose the real question is ... if your setting up such an obvious sneak attack, and the opponent knows about it, what options do they have? If your troops are already in their territory then its too late right? So what are they supposed to do, declare war upon you?

I think there should be an option of "Eject Player X's troops from our borders" option. Is the only possible defense vs sneak attack, which is ALMOST like declaration of war = troops teleport out, although its up to the defender to do such a thing, and it has to be done during peacetime.

Actually I like this idea more and more. At any point in time during a turn you can immediately expell all forces of Player X from your borders ... so long as there are still peaceful relations. So if you move too many troops through an AI's territory they will expel you (or if you move too close to their cities)

Im not saying that there shouldn't be ways to perform a sneak attack, but a sneak attack should be the exception, not the norm. You should provide some significant investment to perform a sneak attack, something significant in both single player as well as multiplayer. (like following a Shadowy cult/Assasins Guild)

Reply #50 Top

If your troops are already in their territory then its too late right? So what are they supposed to do, declare war upon you?

Yes, declare war. Probably declare war before they reach your heartland. A good way to simulate that would be to provide open borders only through certain parts of your territory, like through a corridor leading to common enemy land rather than through all the territory. It might be painful to draw the corridor and would need some uithinking but it could be interesting.

Also, sneak attack would be exceptional as noone would trust you again afterwards so you'd never get another open borders agreement.