1.31 Capture Testing

Had my head buried in server code for a while so I haven’t really seen much of 1.31. So I was in for a few pleasant surprises when I went into the game world to test the preliminary guise of the new capture system, and I had to share :)

It’s taken me a lot longer than it should have – there have been countless distractions. But if I’m really honest, I was also struggling from a bit of writers block. I’d done a mental back-of-napkin design of what I wanted to do in the first minute of Ramp and I originally discussing this. But when I actually sat down to code it, I got mired in “how it works” – where “it” is the old, legacy system.

From the very start, what we planned was a gross simplification of the main – hard-coded – capture system, moving the more complex parts of the system into a more easily management Lua script. Several main reasons:

  1. Easier to implement,
  2. Easier to experiment with and build new rule sets (e.g. unique rules for events/intermissions),
  3. Shorter development time for the more complex part of the process,
  4. Easier for producers etc to extract rules from a Lua script than try to read C++ code.

Finally, this last week, I managed to toss out my preconceptions based on the existing code and implement what needed doing:

The old system works by identifying certain types of buildings in the game world; the tables you hump are actually independent buildings. They just happen to be small and placed inside a capture building. The actual capture building itself wasn’t really associated with capture…

Some time back I had cleaned up the system that deal with these. It used to be messy. Now it was isolated down to a few big chunks of complicated legacy code and a fairly compact system for manipulating the “vis level” of the table object.

In it’s fully repaired state, you see a table + a radio (“RADIO_CAPPABLE”). It becomes just a table in the damaged state (“RADIO_TABLE”). In the destroyed state, nothing is visible (“RADIO_HIDDEN”).

This slightly peculiar approach allowed the system to borrow the pre-existing state system that relays facility and building states around the game cluster and to your clients. Tables are just buildings, as far as the host is concerned, and their state changes just travel along the same pipelines as a firebase going up/down or a building becoming damaged/repaired.

The down side was that the building system itself became littered with special handling and cases for these actually quite distinct buildings.

Ok: So I put that aside, stripped out some huge chunks of overly complex code:

Old system: Capture buildings were “guessed” at, so there was lots of code dealing with making sure.
New system: The buildings are identified at terrain load time, and the capture process is handled at the facility level instead of worrying about buildings. The cell hosts determine when a player is in a capture building.

Old system: All of the building-management code had to ensure it never interfered with a capture object, or that capture object changes interfered with building damage/repair, firebases etc.
New system: Has no special cases.

Old system: Manipulating table/capturable states required special interfaces to the damage system to propagate the information involving countless member functions, direct member accesses and bits of string. Because the interface had “evolved” it required lots of arguments which had to be calculated by various functions to inject at the level they intended to participate.
New system: The strat API for manipulating capture states is clean and compact:

  • facility.setCaptureDisabled()
  • facility.setCapturePending()
  • facility.setCaptureEnabled()
    • Three simple functions that ensure the right thing happens even if you are asking for the wrong thing.
  • facility.setCaptureProgress(percentage, contributor)
    • Updates the progress bar: exposed to the Lua script system only.

The second half of the API is for querying states:

  • facility.canBeCaptured()
    • true/false if the facility is capable of being captured at all
  • facility.isCapturable()
    • true/false if the facility could be captured by someone right now
  • facility.isCaptureEnabled()
  • facility.isCapturePending()
  • facility.getCaptureProgress()
    • For a Pending capture, the seconds until it becomes enabled;
    • For an Enabled capture, the %age towards capture

The code behind the API is equally simple, because all of the special cases and handling went away. These functions manipulate a very simple state engine.

I was even able to then borrow some of that old state engine code for transmitting the underlying state properties of facilities around the network.

All that left was to connect the “guy in a capture building” on a cell host to something in the Lua engine.

For this, I created a simple “CaptureContribution” class. When a player on the cell hosts meets certain criteria, that is packaged up along with things like their brigade, missionid, etc, and shipped to the strat host.

The strat host places these onto a queue and then periodically calls a Lua function, ProcessCaptureContributions.

The resulting Lua script looks roughly like this:

-- We batch-process contributions, but only
-- apply them to their facilities periodically.
-- So we have to track data between runs.
local trackedFacilities = {}

-- This is the function the Strat Host calls.

function ProcessCaptureContributions()

  -- Ensure our table of facilities is populated.
  trackedFacilities.Populate()

  -- Consume the contribution queue.
  trackedFacilities.DrainQueue()

  -- This is the guts of the system: for eligible facilities,
  -- take the capper/defender data and use it to
  -- change the progress state.

  for facility, track in pairs(trackedFacilities)
  do
    if track.nextTick <= time() then
      trackedFacilities.ApplyStats(facility, track.cappers, track.defenders)

      local progress = facility:getCaptureProgress()
      if progress <= 0 or progress >= 100 then
        -- Remove "saved"/capped facilities from tracking.
        trackedFacility[facility] = nil
      else
        -- Don't process this facility again until the
        -- next "capture tick"
        local interval = wwii_config.capture.tickInterval_s
        trackedFacility[facility].nextTick = time() + interval
      end

    end
  end
end

function trackedFacilities.ApplyStats(facility, cappers, defenders)
  -- ===> THIS IS THE GUTS OF THIS SYSTEM <===
  -- At least, the fact that this code is in a script and not
  -- squirreled away in hard coded C/C++ code:

  -- We have a facility, a number of people who generated
  -- "capture contributions" for capturing and defending it,
  -- and access to anything else we want...

  -- In this **testing iteration** we do nothing fancy,
  -- we just check for the presence - or lack of - cappers.

  progress = facility:getCaptureProgress()

  if track.cappers > 0 then
    progress = progress + 1
  else
    progress = progress - 1
  end

  facility:setCaptureProgress(progress)
end

function trackedFacilities.Populate()
  -- Ensure that all of the active facilities are in
  -- our trackedFacilities table (ensures each one has the
  -- fields we're going to work with later).
  --
  -- An "active" facility is one that is currently in the
  -- capturable state.

  for facility in allActiveFacilities() do
    if not trackedFacilities[facility] then
      trackedFacilities[facility] = {
        ['cappers'] = 0,
        ['defenders'] = 0,
        ['nextTick'] = time() + 1
      }
    end
  end
end

function trackedFacilities.DrainQueue()
  -- Total up queued contributions to their facilitys' stats.
  -- Contributions are in the order receieved, not sorted
  -- by facility.

  for contrib in allContributions() do
    track = trackedFacilites[contrib:facility()]
    if track ~= nil then
      if contrib:side() == contrib:facility():side() then
        track.defenders = track.defenders + 1
      else
        track.cappers = track.cappers + 1
      end
    end
  end
end

I don’t know for certain that we’ll stick with any of this methodology, particularly I think we might just decide to apply the contributions as they arrive. Or we might run the script very frequently. On the one hand, if you wait too long between processing batches, you can get weirdness like a building that was cleared of cappers 4 seconds ago still being captured at the end of a 5s tick window. Sure – you can solve that, but it means making the infrastructure more complex.

Remember: the “game world” with players moving around is on multiple cell hosts. The capture system is on the strat host. Its details like this that got me bogged down trying to come up with ways to synchronize that sort of information.

Alternatively, I could have the cell hosts send pre-batched data to the strat host (8 guys were capping in this building for the last 5 seconds). Again, there are multiple cell hosts so these messages would have to be synchronized.

The cell hosts could remember that they had sent capture contributions for some players and send a cancellation event. That’s of no-use if the strat host doesn’t get to them in time, and it requires the cell host ensure it sends the cancellation immediately.

At the moment the cell host sends the contributions at the end of a capture tick. So you have to be in a building for 5s (the current setting) before it sends a capture contribution. In theory, it could then be up to 4.999 seconds before that gets processed.

IMHO the easiest way to solve it will be to have defenders in a building preventing capture progressing (a small tweak to the Lua script). That will take away ambiguity.

It’s been fun putting it together, and it was really enjoyable working in Lua on implementing the first iteration of the rules code. I think there’s a lot of potential for making capture more interesting.

The most notable and immediate change this brings to capture is that it is no-longer about hiding behind a table biting your nails. Instead, capture is dynamic; you can move around and fire. More importantly, the progress bar is not specific to you, it belongs to the facility. Instead of instantly resetting to zero, it slowly ticks back down.

I’m looking for a couple of things from what we finally decide on: I want the decay of the capture bar to encourage people to go back, and generate fights around capture. I also want to see the decay rate increase, and perhaps the capture rate slow, the more facilities you are working off the same armybase to encourage some co-operation.

Eventually, I’d like to see some factoring of the number of people in the speed of capture. But I’m not quite ready to say I “want” that yet.

I’m also undecided about the idea of having defenders contribute to the “uncapture” of a facility. At first that seemed like a good idea to me, but it may be a handicap to defenders to put that in. If its purely automatic, then all they need to do is hold out and keep cappers from getting in again. Sort of like FB auto-repair works right now. I know some people have said they want to be able to repair FBs, but if we add that to the game, it will become … compulsory. “Great, now every time an EI runs into a depot, we have to go uncapture it!”

Monday I have to work on sending state information to the client so that it can render a meaningful progress bar for capture. We already have the class written up and implemented on the client, I just need to populate it and send it.

After that, we’ve got a small change we want to make to MSPs that I think drivers will appreciate, and then I’ll be focusing on China for a while before getting together with Ramp to work on some update system enhancements. We’ve made some decisions for 1.31 that grant us some real opportunity to make significant improvements.

15 Comments

I am really interested in this as I think it seriously could offer up new options and variables for SD.
Of course SD is a buzz word right now since one side has had it continually for so long but this is something I have gone on about for ages.

How easy would it be to have the option to increase or decrease capture times dependent upon new variable “numbers in game or Spawn delay calculation”.

How easy would it be to increase number of cappers dependent upon other variables such as SD.?

This now means we “could” look at the possibility of having differnet capture conditions based upon numbers balance and may make everyone happier.

just wondering if this was a route you had actively been thinking upon for the future ?

Really please to see you getting stuck into this new capture scheme and looking forward to testing and seeing it mature in-game.

*ROFL*

1) you need to spend less time with Doc, your first 30 seconds of talking was full of Australian queriative terminations “I was capturing an army base?”… *giggle*

Good to hear you’re not dead yet tho ;)

Maybe turning off game sound (it’s really hard hearing you over the thunder and rain noise) and either more gamma or try it during a brighter day (it’s fairly dark :)

picky i know

Yeah didn’t want to say KFS1 but it really did sound like an Australian.

A brit moving to America picks up an Australian accent :)

Does the ‘Progress Indicator for the capture of the entire town’ mean that an Attacker will need to capture all flag facilities in town in order to capture said town? Or is it still flip the AB switch in order to flip town ownership? If the attacker has to capture all facilities first, it might be an improvement….might be.

TBH, though, I don’t see the ‘new’ capture system being any different from the present capture system. Sure, you’ve introduced a minor cosmetic change to how an individual player flips a switch (talking gameplay-wise, not code-wise, obviously), but the capture system remains a switch-flipping system where the flipping of those switches are more important to successs then actually fighting/routing the enemy.

Do your UI people also use Lua?

I would like to have something like this for WWIIOL enabling players to customize the UI.
http://us.blizzard.com/support/article.xml?locale=en_US&articleId=21465

I never did any Lua programming, because I knew very early that I would quit playing WOW,
I just played the game because some friends were playing it.
They still are, but I couldn’t keep up with their level grinding.

Capture system sounds great, but that AB is just awful.

Mangy: Way to completely miss the point, but I’ll put it down to difficulty hearing the voice commentary. Let me put it this way: The new system is a deliberate unimplementation of capture.

“A minor cosmetic change to how an individual player flips a switch”. Now you’re just being over-dramatically negative, or you really missed the point of the video. The progress bar belongs to the facility, not the individual player; it is shared. Not only do the defenders see the same progress bar, so do all the cappers.

And changing how it plays out is a matter of tweaking the Lua script – a trivial thing.

Sms: The UI system was going to be a full DOM/CSS XUL based system, so it uses JavaScript. The client does use Lua for the tutorial systems; after I showed Ramp how easy it was to integrate and how much more efficient than JavaScript, in particular how good it was for event-driven systems and – most importantly – that bit more producer friendly… He decided to use it for that.

I don’t think we’re ever going to want to provide remotely close to Blizzard’s customization capabilities. Skinning the HUD/UI, maybe; but the last thing we want is someone users getting an unfair advantage like Carbonite.

sres: Meh – wait until you get your hands on it :) It’s a very nice infantry fight. It’s occupying the same footprint as the old camo-net thing, so it’s not taking anything away from armor :)

Imagine…

  -- Firstly, determine the base "point" value.
  -- Overpop captures go a max of 10% slower,
  -- Underpop defenses go a max of 5% faster
  local capturePt = sideBalanceAdjusted(1, capSide, 0.10)
  local defensePt = sideBalanceAdjusted(1, defSide, 0.05)

  -- Modifiers to points.

  -- 25% faster captures for pockets.
  if facility.cp.isInPocket then capturePt = capturePt * 1.25 end
  -- 10% faster captures if no enemy brigades here.
  if facility.cp.numBrigades() == 0 then capturePt = capturePt * 1.10 end
  -- 2.5% faster defense per army brigade present.
  defensePt = defensePt * (facility.cp.numBrigades(ARMY) * 1.025)
  -- 5% faster defense for every open, outgoing link
  defensePt = defensePt * (facility.cp.openLinks() * 1.05)

  -- 2% slower capture and 5% faster defense for simul-caps (by armybase)
  simulcaps = facility.armybase.capturesInProgress
  if simulcaps > 1 then
    capturePt = capturePt * (1 + (simulcaps / 50))
    defensePt = defensePt * (1 + (simulcaps / 20))
  end

  -- If you have players inside the building, other teammates
  -- within 250m count at 2% of a normal occupant.
  local nearCap = facility.infantry(capSide)
  local outsideCap = nearCap - cappers
  if outsideCap > 0 then
    cappers = cappers + (outsideCap / 50)
  end

  local outsideDef = facility.infantry(defSide) - defenders
  if outsideDef > 0 then
    -- Defenders only count as 1% if this is a spawnable.
    if facility.isSpawnable(defSide) then
      defenders = defenders + (outsideDef / 1)
    else
      defenders = defenders + (outsideDef / 2)
  end
    
  -- If there are no cappers near a part-capped
  -- building, then always add 1 defender so
  -- the building will auto-uncap.
  -- Each time we do this, add slightly more
  -- than one defender
  if nearCap == 0 and progress > 0 then
   if not track.defBonus then
     track.defBonus = 1
   end
   defenders = defenders + track.defBonus
   track.defBonus = track.defBonus * 1.05
   track.nextTick = track.nextTick + 2 * interval
  else
    track.defBonus = nil
  end

  -- You always get some points for having a capper in a building,
  -- but it might not outweigh what the defender gets
  capScore = capturePt * cappers
  defScore = defensePt * defenders

  facility:setCaptureProgress(progress)

This is pure theory crafting. I’m not basing anything on the random gibber I typed above. The numbers are pulled from so far up my ass I gave myself a sore throat retrieving them.

One of the truly neat aspects here is that there are two separate variables at play: rate of capture and rate of defense.

There are limits to how crazy you can get because some rules become exploitable or create negative feedback loops that discourage the goal of creating fights around captures.

For instance… If there are defenders dying within range of a facility, should that speed up or slow down the capture?

If it speeds up the capture, then is that going to encourage the defenders to rally and go in together? Or is it going to be perceived by the average player as a reason not to go near a facility being capped (“don’t give them free capture points”)?

Whereas, if getting shot near a facility being capped slows down the capture, well now the defenders have some actual motivation. Likewise, if being shot by defenders “uncapping” a facility restores some “capture progress”, well now attackers have reason to put pressure on a partially captured facility…

Conversely, though; are you going to make people in capture buildings reluctant to shoot an ei for fear they will slow down their own capture? Do you need to distinguish kills where both players are inside the capture building and those where one or the other isn’t? Should a defender shooting an ei inside the building count towards defense, whereas a capper being killed outside the building slows the defense down?

But those are Artificial motivations to fight around Fixed and Arbitrary Capture Switches.

“those”?

I don’t see a “those”

This is pure theory crafting. I’m not basing anything on the random gibber I typed above. The numbers are pulled from so far up my ass I gave myself a sore throat retrieving them.

There’s always going to be a switch at the back end of capture, because this is a computer system and almost every computing system – except perhaps quantum computers – ultimately boils down to switches.

The beauty of this system is that the controller for the switch is no-longer built in. It is soft. It has access to most of the features/functions of the strat host, it has the ability to build and manage its own tables and tracking. It could have a custom rule for every facility in the game, or individual CPs.

Fantastic.

Yes you may have pulled those figures out of the dark place but I love the fact you have percentages based upon variables.

This means at least that’s in your thought process and maybe something like that will go into a version. faster or slower dependant upon X.

Love it or the possibilities ahead it may bring.

The ‘Those’ I was referring to where your examples to modify Rate of Capture and Rate of Defense that you used. All they’d serve to do is muck up the works.

From a purely aesthetic and visualisation view they look horrific.

From a gameplay side I can see that fights in and around them will be quite interesting.

However attracting new players to the game is difficult if their eyes are bleeding before they get to experience the gameplay.

Leave a Reply

Name and email address are required. Your email address will not be published.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

You may use these HTML tags and attributes:

<a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <pre> <q cite=""> <s> <strike> <strong> 

%d bloggers like this: