Development

Line-Of-Sight Calculation Algorithm

This file describes the line-of-sight calculation between units, and is only of interest to developers.

Theory/model

The board is composed of triangles, each of which has a terrain type and an altitude/slope. Each terrain type has a ‘visibility stack’; this is essentially a list/function that tells how opaque it is at a given altitude.

I.E., all terrain types are 0% transparent at/below ground level. Most types have some obscuration just above that to some height (due to ground cover, grasses, trees) and are 100% clear at higher altitudes. Note that these values are for looking through an entire hex worth of that material, and thus have to be adjusted down for less distance.

The ‘base visibility’ between two (3D) points is inversely proportional to the square of the distance between them (the light/apparent size would drop off as the square of distance), less whatever (cumulative) attenuation is caused by terrain obscuration; if I look through two full hexes of 80% visibility, it reduces visibility to 64%.

Why triangles and not hexes? Three reasons: 1) Math is much easier with triangles. 2) Some hexes appear to be composed of multiple terrain types, and we can approximate more closely by subdividing into triangles. 3) If we break into triangles, we can give each a constant slope and have the map be continuous.

Computation

We take our two points, and assume the watcher/target to be 3M tall. Consider the 3D line between them; this is the literal LoS. Taking just the 2D (top-down) view of that line, we figure out all the triangle edges it crosses, in order. The line segments between consecutive pairs of these points each cross a single triangle.

Now, start with the base visibility (constant / distance ^2). We then run through all the triangles we cross, figuring out how much they obscure the view. Well, how much is that?

First, we figure out which triangle we’re crossing; take the midpoint of the segment and it’s in that triangle. Using the 3D version of the LoS vector again, take the worst (most obscured) altitude for the segment, which will be the lowest, and (because the triangles are flat) will be at one of the two points where we enter/exit the triangle. Compare that to ground level at that point to figure out the height above ground level, then look that up in the terrain visibility ‘stack’ for the terrain type in this triangle.

Now, we have to adjust the visibility loss for the distance covered by that segment - this is a power thing, rather than linear, because of the way we multiply visibility together. (For instance; if looking through a full hex-width of forest gives you 50% visibility, looking through half that gives you ~70%, because two half-hexes would be 70%*70% = 50%.) Possibly clever things with logarythms might make all this power-fiddling into addition, but that makes my head hurt.

Anyhow, you now multiply the base visibility by all of the segments’ visibility, and that’s your result. Ta-Da! Being able to see further from hilltops, and not see through hills etc, should fall out automagically if the altitudes and terrain data are set up sensibly.

  • mikee

Layer Refreshing Algorithm

This file describes the layer refreshing system of the game, and is only of interest to developers.

Background

The various panels in the game are called layers, which are created on game startup and never destroyed during game-play. Instead, they are set visible or invisible.

Layers can be made moveable by subclassing FloatingWindowLayer instead of Layer. All Layers can be partially transparent

The refreshing algorithm supports/will support refreshing the internal information, a part of the layer, or the whole layer. The main logic is in playfield.py/paint(), and the redraw itself happens in each layer’s paint() method. If a layer wishes to use a border, it can do so by calling self.paintBorder() in its paint() method.

When in doubt, the layer can always update everything.

Internal Refreshing

So what is internal refreshing? Internal refreshing means that the layer is in a _known good state_ from a graphical point of view. This requires that a) no other layer has partially covered it, and b) the layer itself chooses to use the internal refreshing system.

If either of these are untrue, a full redraw is issued, otherwise the layer can update only the parts that have changed from the previous time. In practice this means knowing what a layer has drawn the previous time, usually using variables.

Refreshing

Usage

Each layer has the variables * self.x * self.y * self.width * self.height * self.need_internal_repaint (readonly)

The four first describe the bounding box of the layer (without the surrounding border), and must be updated accordingly, before asking for a refresh.

A graphical refresh is requested with * playfield.needInternalRepaint ( layer ) * playfield.needRepaint () * playfield.needRepaint ( pygame.Rect )

needInternalRepaint() asks for an internal update, needRepaint () requests a complete repaint of the whole playfield, needRepaint ( pygame.Rect ) adds the given rectangle to the list of dirty areas that need to be updated, and can be used to add as many areas as one wishes.

The repaint is issued with playfield.paint(), which contains all the logic needed to issue the right orders to all layers.

The variable need_internal_repaint can be read in the layer’s paint() method to check if the current context only requires an internal update.

It is always safe to redraw everything in the layer.

There is no way to make a part of the layer transparent in the paint() method, you’re too late and should’ve called playfield.needRepaint (), or even better, playfield.needRepaint ( layer.getRect() ) to make the layers below refresh.

Current Issues, Specific to Civil

  • The terrain can’t paint the dirty areas, it repaints

everything. Slooow. Current suggestion is to have a copy of the current map in a surface, and blit straight from the there to the appropriate dirty area(s).

  • Some layers don’t know what they are supposed to paint,

until they get called in the paint routine. This makes unit layers, weapon range layers etc a bit slower than needed, since unneeded refreshing of everything must be done.

  • Sometimes a refresh is issued even though there is

no need to, or it could be optimized to only cover a certain area. This goes away slowly

  • After a “unsuccessful” select, two refreshes are

issued. ( around half a second on my computer…)

Bugs

OPEN BUGS

Units seem to be able to run through water. The internal terrain data is apparently not ok?

Retreating, routed and moving units will stop and drop all their orders when they run into water. Maybe the retreating/routing units should get some other place to move to?

Ending screen looks horrible. The font is too large.

The weapon description text in the unit status window can overflow.

OPEN OLD BUGS

Some in-game dialogs can be activated twice. For instance if the player presses F1 twice in a row then two HelpBrowser states are activated. Closing the help browser dialog still leaves the game in the second HelpBrowser state, but without any visual clue as to what is going on. Hitting Escape closes the second HelpBrowser state too. States should check for this? UPDATE: At least HelpBrowser can’t be activated twice, see state.py how I “fixed” this… –msa

The “wait” cursor used in the setup screens lacks the white color. The cursor files seem to be somehow wrong. Need to check the cursor and fix. It works, but is mostly invisible…

Changing size (to smaller at least) leaves some artifacts on the right border. Seems like parts of hexes? - Yes, correct.

Noticed a weird combination of ending the turn and immediately dragging windows. It mixed up somehow, the action phase play buttons came available but I still had to end the turn again. Will test more later.

“full-screen” gfx pictures must take into account window size => center gfx, black borders around it? (e.g. take big resolution, surrender game)

Exiting Civil from the front dialogs via the window manager can leave the AI client running.

MSA’s NITPICKING SECTION (can be considered as “enhancements, not bugs”, sometimes of questionable value…)

G’s Nitpicks:

Running Civil at a lower res means the Game Over screen appears at the lower res. As such, buttons and info is positioned off screen…


ON HOLD FOR NOW

Clearer visual signs when we are in action mode, and when in “real” play mode. Clearer visual signs how to end turn, and how to end action phase. - what can we do about this? WONTFIX


FIXED BUGS

Compiling the C LOS extension should be done using distutils. FIXED

The server and AI client will be left running when the human player exits the game. The client should send out some kind of “End game” message? FIXED

Retreating state-machine is wrong. Still refers to “retreatinginfantry” etc. FIXED

engine/ai/rally.py will rally units that are disorganized. But it doesn’t know wether the unit should be, say, limbered or unlimbered. Thus we should maybe have “disorganized_limbered” instead of “disorganized_artillery”. FIXED

Better modes for units. A few of the current ones are a bit silly. Instead of having “meleeing_infantry” we should have “meleeing_formation” and “meleeing_column” so that we know the original mode of the unit, and what mode it should get after the melee is over. Same goes for a lot of modes. FIXED

Clicking “Load game”, then “Cancel”, followed by “New Game” and “Scenario” results in no scenarios being listed… FIXED

The input dialog in-game crashes. At least when I tried to change the name of a saved screenshot. Should be a simple fix. FIXED. Requires Pygame 1.5.3+

The in-game dialog “Change combat policy” keeps drawing over itself over and over again, making the text thicker and thicker. It should clear the area before drawing.

Game loading should work. Will try before 0.80 milestone. FIXED. Old saved games are invalid though. Or you need to fix to .

Some dialog layers seem to assume the screen is 1024 pixels wide, as they use the magic constant 512 for calculations. Will break when the screen size is changed. FIXED

The flag scenario.playing is not handled in event_loop.py => Civil will never actually quit when doing an “Quit Civil”. FIXED

LOS doesn’t work at all? Try scenario10, move unit to the west where the enemies should be. Nothing is displayed. - this should work now. There was a missing unit.calcView() that meant that

units never recalculated what they saw.

Num Lock may not be pressed in-game. Nothing works if it is. Strangely all works during setup even if it’s selected. FIXED

During action phase unit info window does not update the info. Needs to connect a signal. FIXED

Changing combat policy dialog has “wrong offset” when clicking the checkboxes. FIXED

Choosing units doesn’t work properly when the map has scrolled away from (0,0). Some offset bug… FIXED

The little yellow rectangle in the minimap is too small. It does not take into account the fact that the panel is no more. FIXED

Help browser shouldn’t just raise & quit if a help file can’t be parsed. (or some section is missing) FIXED

Scenario format

This appendix describes the format of the scenario files. The files are based on XML files and fairly easy to modify by hand if needed.

The scenario files the Civil uses are normal Zip files that contain two or three files:

scenario.xml which contains the full data for the scenario, along with the map, all unit, objetcives, general info etc.

info.xml which contains the contents of the tag >scenarioinfo< extracted in a separate file. This is for eacy access so that the whole scenario.xml does not have to be parsed when info about the scenario is needed.

los.pickle which is line-of-sight data in pickled form. This file does not need to be here, as it is merely a cache for information that can be recalculated when the scenario is loaded.

The scenarios are compressed with Zip as that makes them smaller and it is also a convenient way to merge several files into one archive. XML as a format is easy to use and makes the scenarios structured, but it also makes them large. Compressing them makes them smaller, thus conserving a lot of disk space and making them a lot faster to load over a network if needed.

The following sections will list all the tags in the XML file and describe what data they contain. All the tags are inside the mandatory toplevel tag <scenario>, with a normal XML header.

This tag is mandatory. It gives some basic info about the scenario, such as textual descriptions och the scenario and the missions, as well a the date and length of the battle. The data in this tag is copied verbatim into the shorter foo-info.xml file. The contents of the <scenarioinfo> is shown to the player when selecting a scenario.

This tag contains the name of the scenario. It is a string that is only shown to the players, it is not used internally in Civil.

This tag gives a one line description of the location of the battle. It is shown to the players only and not used by Civil internally. This should be the geographical location of where the battle took place, such as Gettysburg.

This tag gives the starting date and time for the start of the battle. It has no other data apart from five attributes: year, month, day, hour and minute. Each attribute has a numerical value. A 24 hour clock is used for the hour. This data is used by Civil.

This tag gives the current date and time for the start of the battle. For a new battle this is the same as the starting date, but for a saved game this is the current date at the time of the save It has no other data apart from five attributes: year, month, day, hour and minute. Each attribute has a numerical value. A 24 hour clock is used for the hour. This data is used by Civil. The game is over when the internal clock passes this date.

This tag gives the ending date and time for the start of the battle. It has no other data apart from five attributes: year, month, day, hour and minute. Each attribute has a numerical value. A 24 hour clock is used for the hour. This data is used by Civil. The game is over when the internal clock passes this date.

This tag describes the scenario. It gives a general description of the scenario and can contain information such as historical or fictional info that led to the battle, or other background info. For a historical scenario it could also contain info about that happened in the real battle.

The actual data is any number of <para> tags. The <para> tags are paragraphs of the description, and there is normally least one such tag, unless the scenario has no description.

The data in the <para> tags is only meant for viewing by the players, it is not parsed by Civil in any way.

This tag describes the missions for the rebel and the union player. It consists of two subtags: <rebel> and <union>, and they in turn have any number of <para> tags. The <para> tags are paragraphs of the description, and there is at least one such tag in both <rebel> and <union>, unless the side has no mission description.

The data in the <para> tags is only meant for viewing by the players, it is not parsed by Civil in any way. The mission descriptions should brief the players as to what their mission in the scenario is.

The data in this tag describes all the weapons used by various units in the scenario. This tag contains any number of <weapon> tags, which in turn has a number of attributes that describe the weapon:

  • id which is the unique id assigned to the weapon. Units refer to this id when their weapon is described ()
  • name which is the name of the weapon, such as Rifle. Not used by Civil.
  • type gives the type of weapon. The current types available are rifle and artillery. These affect how the weapon is used.
  • range gives the maximum range of the weapon i meters.
  • damage gives a value that describes the damage done by the weapon when it hits an enemy unit.
  • accuracy which gives the accuracy of the weapon at a given range. TODO: what range?

All weapons need not be used by units, but all weapon id:s that units refer to must be present.

This tag contains all the unit data. The units are grouped inside the two subtags <union> and <rebel>. Full unit organisations are used. The data in the <union> and <rebel> tags is identical, the two tags are only used to give the owner of the organisation. No other tags nor attributes are used.

The units are always organised into brigades. Each brigade is organized into a number of regiments. A regiment has either a number of battalions or companies. A battalion contains companies.

Brigades, regiments and battalions do all have a <headquarter> tag. It defines the data for the commander of the organisation, and it looks the same for all organisations. Each organisation thus must have a headquarter, regardless of how man suborganisations it contains. Companies do not have headquarters.

A <headquarter> tag basically defines the same data as a company (), but with one added tag, namely the

<commander> tag. All other tags are same for units.

The <commander> tag defines the data for the commander in a headquarter unit. The headquarter unit is merely a wrapper around the commander, and contains some lower officers. The <commander> tag has a name attribute which is the human name of the commander. Additionally it has a few subtags which define properties for the commander.

  • rank defines the name of the rank of the commander in a human readable form, such as Captain. It has no attributes.
  • experience defines the experience of the commander. A more experienced commander makes less mistakes.
  • aggressiveness defines the aggressiveness skill of the commander, ie. the likelihood of the commander doing something radical.
  • rally defines the rally skill of the commander.
  • motivation defines the motivational skill of the commander.
  • All the above tags except rank have an attribute value and no main data. The attribute contains a numerical value for the modifier that it represents.

Brigades are the largest forms of organisations in Civil. Every other organisation is a part of some toplevel brigade. The only visible part of a brigade is the headquarter unit.

A <brigade> tag has the attributes id which defines a unique id for the organisation, and a name which contains a human readable name for the organisation.

Several regiments build up a brigade. The only visible part of a regiment is the headquarter unit.

A <regiment> tag has the attributes id which defines a unique id for the organisation, and a name which contains a human readable name for the organisation.

Several battalions build up a regiment. Battalions are however not a mandatory organisation, as a regiment may not have any battalions at all, instead it can contain companies. If battalions are present in a regiment, the battalions contain all the companies of the regiment.

The only visible part of a regiment is the headquarter unit.

A <battalion> tag has the attributes id which defines a unique id for the organisation, and a name which contains a human readable name for the organisation.

Companies build up the bulk of the visible units in Civil. A company is the lowest level organisation available, in Civil, and it has no separate headquarter unit.

A <company> tag has the attributes id which defines a unique id for the organisation, a type and a name which contains a human readable name for the organisation. The type can have one of the values infantry, cavalry and artillery, and it defines the type of company. The <company> tag also contains these subtags:

  • commander which defines the commander of the company. A company has no separate headquarter unit and thus has its commander in the unit itself. See <xref linkend=”scenario-tag-units-headquarter”/> for more info about the <commander> tag.
  • pos which defines the position of the unit. It has the attributes x and y which give the pixel coordinate, and no main data.
  • men which defines the number of men in the unit. The attributes ok and killed give the number of men that are eligible for combat, and the number of killed men. The tag has no main data.
  • facing which defines the facing of the unit using an attribute value. It has a value from 0 to 35. The tag has no main data.
  • morale which defines the morale of the unit in an attribute value. The value is a numerical value. The tag has no main data.
  • fatigue which defines the fatigue of the unit in an attribute value. The value is a numerical value. The tag has no main data.
  • experience which defines the experience of the unit in an attribute value. The value is a numerical value. The tag has no main data.
  • weapon which defines the main weapon for the unit. It has the attributes id, ok and destroyed. The two latter give the number of weapons that are still useable and the number of weapons that have been destroyed. The id refers to a weapon as given in the <weapons> tag.

Optionally a company may contain a tag <arrives> which indicates which turn the unit will arrive at the battlefield. The tag has no data, only five attributes: year, month, day, hour and minute. These indicate the time when the company is shown on the battlefield. If this tag is not present the unit is present at the battlefield when the battle starts, and if the tag is present then the unit will arrive at the position indicated by <pos> at the specified turn.

This tag contains a list of special named locations in the map. These locations can be known hills, rivers, bridges, villages etc. The locations are given as any number of <location> tags. The <location> tags have two attributes x and y. These give the position of the location in the map in pixel coordinates. The data of the tag is the text that is to be shown on the map.

Locations have no other function apart from adding to the atmosphere of the scenario, and are not used by Civil in any other way than rendered.

This tag contains the objectives of the scenario. There may be any number of objectives in the scenario, and they are used as special positions on the map that are valuable. An objective can be an important hill, bridge, road crossing or just about anything. The objectives are rendered in the map using special icons to make it easy to spot them.

The objectives are given as a series of <objective> tags. Each <objective> tag contains the following attributes:

  • id is used as an unique id for the objective.
  • x gives the x position of the objective in pixel coordinates.
  • y gives the y position of the objective in pixel coordinates.
  • name gives a short name for the objective. This name may be shown in the map.
  • points which gives the number of points that the objective is worth.
  • owner which gives the current owner of the objective. The owner has captured the objective, and will be awarded points for it unless the opponent captures it. The possible values are union, rebel and unknown.

This tag contains all the map data. It is usually the largest single section in a scenario. Apart from the map data it also contains any extra features that the map may have. The only attributes the <map> tag has are xsize and ysize which define the size of the map. The <map> tag has a subtag <hexes> and an optional subtag <features>.

This tag defines some optional features that may or may not be used in a map. If it is present it contains a numer of <feature> tags, one for each specific feature. A feature can be a house, trench or something similar that isn’t raw terrain. The <feature> tag has a few attributes and no main data:

  • id which defines a unique id for the feature. It is used from within Civil as an icon id.
  • x defines the x pixel position of the feature.
  • y defines the y pixel position of the feature.
  • type defines the type of feature. TODO: what types are there?

This tag contains all the map data. It is a long list of <hex> subtags. It has no attributes. Each <hex> tag contains the following attributes and no main data:

  • x defines the x position of the hex in the map.
  • y defines the y position of the hex in the map.
  • terrain defines the terrain icon for the hex.