Skip to content

Instantly share code, notes, and snippets.

@arturaz
Created April 19, 2012 11:28
Show Gist options
  • Save arturaz/2420357 to your computer and use it in GitHub Desktop.
Save arturaz/2420357 to your computer and use it in GitHub Desktop.
class RaidSpawner
# It's a regular planet.
KEY_PLANET = 'planet'
# It's a battleground planet.
KEY_BATTLEGROUND = 'battleground'
# Apocalypse is raging in the galaxy.
KEY_APOCALYPSE = 'apocalypse'
def initialize(planet)
@planet = planet
end
# Creates raiders and raids planet. Registers new raid.
def raid!
raiders = units
unless raiders.blank?
Unit.save_all_units(raiders, nil, EventBroker::CREATED)
Cooldown.create_unless_exists(
@planet.location_point, Cfg.after_spawn_cooldown
)
end
register!
end
private
# Registers next raid on planet.
def register!
@planet.next_raid_at += Cfg.raiding_delay_random
@planet.raid_arg = generate_arg
CallbackManager.register_or_update(
@planet, CallbackManager::EVENT_RAID, @planet.next_raid_at
)
@planet.delayed_fire(@planet, EventBroker::CHANGED,
EventBroker::REASON_OWNER_PROP_CHANGE)
@planet.save!
end
# Return raid configuration key for spawners planet.
def key
return KEY_APOCALYPSE if apocalypse?
return KEY_BATTLEGROUND if battleground?
KEY_PLANET
end
# Returns raid argument that should be stored to database.
def generate_arg
return 0 if @planet.player_id.nil? || apocalypse? ||
! without_locking { @planet.solar_system }.player_id.nil?
arg = battleground? \
? @planet.player.bg_planets_count : @planet.player.planets_count
[arg, Cfg.raiding_max_arg(key)].min
end
# Return array of built (but not saved) units.
def units
definitions = unit_counts
units = []
definitions.each do |type, count, flank|
klass = "Unit::#{type.camelcase}".constantize
count.times do
unit = klass.new(:level => 1, :location => @planet, :flank => flank)
unit.skip_validate_technologies = true
units.push unit
end
end
units
end
# Return what units will raid player if he has _planets_ planets.
#
# Returns Array:
# [
# [type, count, flank],
# ...
# ]
#
def unit_counts
# No raiders if apocalypse and planet is not occupied.
return [] if apocalypse? && @planet.player_id.nil?
units = {}
max_flanks = Cfg.max_flanks
raid_arg = apocalypse? ? apocalypse_raid_arg : @planet.raid_arg
params = {'arg' => raid_arg}
calc = lambda do |formula|
begin
value = CONFIG.safe_eval(formula, params)
# If we went to complex numbers, that means we did something strange and
# the result is bad.
return 0 if value.is_a?(Complex)
[0, value.round].max
rescue FloatDomainError; 0; end
end
Cfg.raiding_config(key).each do
|type, (from_formula, to_formula, chance_formula)|
units[type] = {}
max_flanks.times { |flank| units[type][flank] = 0 }
from = calc[from_formula]
to = calc[to_formula]
chance = [calc[chance_formula], 1.0].min
# Add the minimum.
from.times do
flank = rand(max_flanks)
units[type][flank] += 1
end
# Add additional ones.
(to - from).times do
if rand <= chance
flank = rand(max_flanks)
units[type][flank] += 1
end
end
end
raiders = []
units.each do |type, flanks|
flanks.each do |index, count|
raiders.push [type, count, index] unless count == 0
end
end
raiders
end
# Has the apocalypse started?
def apocalypse?
@_apocalypse ||= without_locking do
@planet.solar_system.galaxy.apocalypse_started?
end
end
# Current apocalypse day.
def apocalypse_raid_arg
without_locking do
@planet.solar_system.galaxy.apocalypse_day(@planet.next_raid_at)
end
end
def battleground?
@_battleground ||= without_locking { @planet.solar_system.battleground? }
end
end
require File.expand_path(
File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
)
describe RaidSpawner do
methods = %w{register! key generate_arg units unit_counts}
before(:all) { RaidSpawner.class_eval { public *methods } }
after(:all) { RaidSpawner.class_eval { private *methods } }
let(:battleground_planet) do
ss = Factory.build(:battleground)
Factory.build(:planet, :solar_system => ss,
:player => Factory.create(:player, :bg_planets_count => 3))
end
let(:apocalyptic_planet) do
galaxy = Factory.build(:galaxy, :apocalypse_start => 5.6.days.ago)
ss = Factory.build(:solar_system, :galaxy => galaxy)
Factory.build(:planet, :solar_system => ss,
:next_raid_at => 5.days.from_now, :player => Factory.create(:player))
end
describe "#raid!" do
let(:planet) { Factory.create(:planet, :next_raid_at => Time.now) }
let(:spawner) { RaidSpawner.new(planet) }
describe "when raiders exist" do
let(:units) { [Factory.build(:unit_built)] }
before(:each) do
spawner.stub(:units).and_return(units)
end
it "should save raiders to database" do
Unit.should_receive(:save_all_units).
with(units, nil, EventBroker::CREATED)
spawner.raid!
end
it "should create cooldown in planet" do
Cooldown.should_receive(:create_unless_exists).and_return do |lp, time|
lp.should == planet.location_point
time.should be_within(SPEC_TIME_PRECISION).
of(Cfg.after_spawn_cooldown)
end
spawner.raid!
end
end
describe "when raiders do not exist" do
before(:each) do
spawner.stub(:units).and_return([])
end
it "should not try to save raiders to database" do
Unit.should_not_receive(:save_all_units)
spawner.raid!
end
it "should not check location for combat" do
Combat::LocationChecker.should_not_receive(:check_location)
spawner.raid!
end
end
it "should register new raid" do
spawner.should_receive(:register!)
spawner.raid!
end
end
describe "#register!" do
let(:planet) { Factory.create(:planet, :next_raid_at => 10.days.ago) }
let(:spawner) { RaidSpawner.new(planet) }
it "should shift #next_raid_at to the future" do
next_raid_at = planet.next_raid_at
spawner.register!
planet.reload
range = Cfg.raiding_delay_range
range = (next_raid_at + range.first)..(next_raid_at + range.last)
range.should cover(planet.next_raid_at)
end
it "should set #raid_arg" do
spawner.stub!(:generate_arg).and_return(115)
lambda do
spawner.register!
planet.reload
end.should change(planet, :raid_arg).to(115)
end
it "should register a callback" do
spawner.register!
planet.should have_callback(CallbackManager::EVENT_RAID,
planet.next_raid_at)
end
it "should dispatch a changed event" do
should_fire_event(planet, EventBroker::CHANGED,
EventBroker::REASON_OWNER_PROP_CHANGE) do
spawner.register!
end
end
end
describe "#key" do
it "should return apocalypse key it it has started" do
RaidSpawner.new(apocalyptic_planet).key.
should == RaidSpawner::KEY_APOCALYPSE
end
it "should return battleground key if planet is in bg solar system" do
RaidSpawner.new(battleground_planet).key.
should == RaidSpawner::KEY_BATTLEGROUND
end
it "should return planet key if planet is in regular solar system" do
RaidSpawner.new(Factory.build(:planet)).key.
should == RaidSpawner::KEY_PLANET
end
end
describe "#generate_arg" do
it "should return 0 if planet is npc" do
RaidSpawner.new(Factory.build(:planet)).generate_arg.should == 0
end
it "should return 0 if apocalypse has started" do
RaidSpawner.new(apocalyptic_planet).generate_arg.should == 0
end
it "should return 0 if planet is in player owned solar system" do
solar_system = Factory.create(:home_ss)
planet = Factory.create(:planet, :solar_system => solar_system)
RaidSpawner.new(planet).generate_arg.should == 0
end
describe "if it's a battleground planet" do
it "should return player bg planet count" do
RaidSpawner.new(battleground_planet).generate_arg.
should == battleground_planet.player.bg_planets_count
end
it "should return limit max arg" do
max = Cfg.raiding_max_arg(RaidSpawner::KEY_BATTLEGROUND)
battleground_planet.player.bg_planets_count = max + 1
RaidSpawner.new(battleground_planet).generate_arg.
should == max
end
end
describe "if it's a regular planet" do
let(:planet) do
Factory.build(
:planet, :player => Factory.create(:player, :planets_count => 5)
)
end
it "should return player planet count " do
RaidSpawner.new(planet).generate_arg.
should == planet.player.planets_count
end
it "should return limit max arg" do
max = Cfg.raiding_max_arg(RaidSpawner::KEY_PLANET)
planet.player.planets_count = max + 1
RaidSpawner.new(planet).generate_arg.should == max
end
end
end
describe "#units" do
before(:all) do
@planet = Factory.create(:planet)
spawner = RaidSpawner.new(@planet)
def spawner.unit_counts
[
["gnat", 2, 0],
["glancer", 1, 1],
]
end
@units = spawner.units
end
it "should place units in planet" do
@units.each { |unit| unit.location.should == @planet.location_point }
end
it "should set unit flanks" do
@units.map(&:flank).should == [0, 0, 1]
end
it "should set unit levels" do
@units.each { |unit| unit.level.should == 1 }
end
it "should set unit to full hp" do
@units.each { |unit| unit.hp.should == unit.hit_points }
end
it "should not belong to any player" do
@units.each { |unit| unit.player.should be_nil }
end
it "should be correct types" do
@units.map { |u| u.class.to_s }.should == [
"Unit::Gnat",
"Unit::Gnat",
"Unit::Glancer",
]
end
it "should not be saved" do
@units.each { |unit| unit.id.should be_nil }
end
end
describe "#unit_counts" do
describe "in normal conditions" do
it "should return data using SsObject#raid_arg" do
with_config_values('raiding.raiders.planet' => {
"gnat" => [
"arg * 2", "arg * 4", "0.3 + (arg - 1) * 0.2"
]
}) do
spawner = RaidSpawner.new(Factory.build(:planet, :raid_arg => 2))
counts = spawner.unit_counts.inject({}) do
|hash, (type, count, flank)|
hash[type] ||= 0
hash[type] += count
hash
end
(4..8).should cover(counts['gnat'])
end
end
end
describe "in apocalypse" do
it "should return data using RaidSpawner#apocalypse_raid_arg" do
with_config_values('raiding.raiders.apocalypse' => {
"gnat" => [
"arg", "arg", "0.3 + (arg - 1) * 0.2"
]
}) do
# Set to nonsense to check that we're not using it.
apocalyptic_planet.raid_arg = 9999
spawner = RaidSpawner.new(apocalyptic_planet)
counts = spawner.unit_counts.inject({}) do
|hash, (type, count, flank)|
hash[type] ||= 0
hash[type] += count
hash
end
counts['gnat'].should == 7 + 5
end
end
it "should return [] if planet is unoccupied" do
apocalyptic_planet.player = nil
spawner = RaidSpawner.new(apocalyptic_planet)
spawner.unit_counts.should == []
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment