--- /dev/null
+# Buff definitions
+name: buff
+defaults:
+ symbol: tanks/effects/buff.Buff
+ timeout: -1 # the Infinity literal is not valid JSON
+ priority: 0
+ stack_limit: 1
+types:
+ speedup:
+ name: Speed Up
+ desc: Speeds up your tank temporarily.
+ tags: [ 'movement' ]
+ stats:
+ move: 2.0
+ effects: []
+ art:
+ icon: ''
+ inv_icon: ''
+
--- /dev/null
+# Item definitions
+name: item
+defaults:
+ symbol: tanks/thing/item.Item
+types:
+ nitro:
+ name: Nitro
+ desc: Speeds up your tank temporarily.
+ tags: [ 'movement' ]
+ buffs: [ 'speedup' ]
+ art:
+ map_icon: ''
+ inv_icon: ''
+
--- /dev/null
+# Unit definitions
+name: unit
+defaults:
+ symbol: tanks/thing/thing.Thing
+ level: 1
+ stats:
+ hp : 1 # health
+ move : 1.0 # move speed (squares/sec)
+ power : 1 # attack power
+ speed : 0.5 # attack cool (sec)
+ accuracy : 1.0 # chance of shooting where aiming
+ shots : 5 # max projectiles in the air at once
+ sight : 5 # distance this unit can see (squares)
+ cooldowns:
+ attack: stats.move.val
+ ai:
+ path : 1.0 # calculate a path to enemy
+ dodge : 1.0 # dodge an incoming bullet
+ shootIncoming : 0.5 # shoot down incoming bullet
+ shootEnemy : 0.75 # shoot at enemy tank if in range
+ art:
+ icon: ''
+ inv_icon: ''
+types:
+ player:
+ name: Player
+ desc: "Don't hate the Player, hate the--Wait. No, that's fine."
+ tags: [ 'tank' ]
+ symbol: tanks/thing/player.PlayerTank
+ stats:
+ hp : 1
+ move : 0.90
+ power : 1
+ speed : 0.5
+ shots : 5
+ pink:
+ name: Pink Tank
+ desc: A very pink tank.
+ tags: [ 'tank' ]
+ symbol: tanks/thing/tank.Tank
+ stats:
+ hp : 1
+ move : 0.75
+ power : 1
+ speed : 0.5
+ shots : 4
+ ai:
+ path : 1.0
+ dodge : 1.0
+ shootIncoming : 0.5
+ shootEnemy : 0.75
+
+
#!/usr/bin/env paver
from paver.easy import *
+import yaml, json, os
+BUILD_DIR = path('build')
SRC_DIRS = [ path('src')/d for d in ('Y', 'ezl', 'tanks') ]
+DATA_DIR = path('data')
def commonjs(*args, **kw):
if v is not True: kwargs.append(str(v))
return sh('commonjs %s' % ' '.join( SRC_DIRS + kwargs + list(args)), capture=capture)
-
-
@task
-def clean():
- "Cleans dep cache and build files"
- commonjs(clean=True)
+def data():
+ "Convert all yaml files to json."
+ for dirpath, dirs, files in os.walk(DATA_DIR):
+ indir = path(dirpath)
+ outdir = BUILD_DIR/dirpath
+ if not outdir.exists():
+ outdir.makedirs()
+ for f in files:
+ if f.endswith('.yaml'):
+ in_ = indir / f
+ out = outdir / f.replace('.yaml', '.json')
+ with in_.open('rU') as infile, out.open('w') as outfile:
+ json.dump(yaml.load(infile), outfile, indent=4)
+
@task
+@needs('data')
def build():
"Builds the Tanks project"
tags = commonjs(script_tags=True, capture=True)
with path('www/deps.html').open('w') as f:
f.write(tags)
-auto = build
+@task
+def clean():
+ "Cleans dep cache and build files"
+ commonjs(clean=True)
+ build()
+
if ( Y.isFunction(o.clone) )
return o.clone();
+ if ( Y.isArray(o) )
+ return o.map(deepcopy);
+
+ // FIXME: Should really be taking appropriate steps to copy
+ // builtin Objects like RegExp, Date & DOM crap
+ if ( !Y.isPlainObject(o) )
+ return o;
+
var path = new Y.YArray();
return core.reduce(o, function _deepcopy(acc, v, k){
var chain = path.push(k).join('.');
if ( v && Y.isFunction(v.clone) )
acc.setNested(chain, v.clone());
+ // Nested array -- recurse, but restart path stack
+ else if ( Y.isArray(v) )
+ acc.setNested(chain, v.map(deepcopy));
+
// Nested object -- recurse
else if ( Y.isPlainObject(v) )
core.reduce(v, _deepcopy, acc);
YObject.fn[name] = fn.methodize();
});
+//#exports ensure getNested getNestedMeta setNested
, classStatics = [ 'instantiate', 'fabricate', 'subclass' ]
, classMagic = [
'__bases__', '__initialise__', '__class__', 'constructor', 'className',
- '__emitter__', '__static__', '__mixins__'
+ '__emitter__', '__static__', '__mixins__', '__id__'
] //.concat(classStatics)
-, mixinSkip = [ '__mixin_skip__', 'onMixin', 'onCreate', 'init'
+, mixinSkip = [
+ '__mixin_skip__', 'onMixin', 'onCreate', 'init'
].concat(classMagic)
, GLOBAL_ID = 0
,
*/
ConstructorTemplate =
exports['ConstructorTemplate'] =
-function ConstructorTemplate(_args) {
+function ConstructorTemplate() {
var instance = this
- , cls = arguments.callee
+ , A = arguments
+ , cls = A.callee
, caller = unwrap(cls.caller)
- , args = (caller === instantiate) ? _args : arguments
+ , args = (caller === instantiate) ? A[0] : A
;
// Not subclassing
if ( caller !== Y.fabricate ) {
- instance.id = GLOBAL_ID++;
+ instance.__id__ = GLOBAL_ID++;
// Perform actions that should only happen once in Constructor
instance.__emitter__ = new Emitter(instance, cls);
--- /dev/null
+//#ensure "jquery"
+var Y = require('Y').Y
+, evt = require('evt')
+, getNested = require('Y/types/object').getNested
+, deepcopy = require('Y/types/object').deepcopy
+,
+
+
+DataFile =
+exports['DataFile'] =
+new evt.Class('DataFile', {
+ __bind__ : [ 'onLoad' ],
+
+ path : '',
+
+
+ init : function initDataFile(path){
+ this.path = path;
+ },
+
+ load : function load(){
+ this.fire('load', this);
+ jQuery.getJSON(this.path, this.onLoad);
+ return this;
+ },
+
+ resolve : function resolve(spec){
+ var nameParts = spec.split('.')
+ , modName = nameParts.shift()
+ ;
+ if (!(modName && modName.length && nameParts.length))
+ this.die('Cannot resolve symbol: '+spec);
+
+ var module = require(modName)
+ , symbol = getNested(module, nameParts)
+ ;
+
+ if (!symbol)
+ this.die('Unable to locate class specified by symbol (symbol="'+spec+'", module='+module+' --> '+symbol+')!');
+ if (!Y.isFunction(symbol.speciate))
+ this.die('Cannot create types from data (symbol-class '+symbol+' from "'+spec+'" is not Speciated)!');
+
+ return symbol;
+ },
+
+ process : function process(data){
+ this.data = data;
+ this.fire('process', data);
+ console.group('DataFile: processing '+this.path);
+
+ var types = Y(data.types)
+ , defaults = data.defaults || {};
+ if (!(types && Y.isFunction(types.forEach)))
+ this.die('Specified types are not iterable! '+data.types);
+
+ types.forEach(function(kv, id){
+ // TODO: process 'inherits' key -- requires resolving data-type path
+ var props = Y.extend({}, deepcopy(data.defaults), kv)
+ , symbol = props.symbol;
+
+ if (!symbol)
+ this.die('No symbol defined for type "'+id+'" at '+this.path+'!');
+
+ delete props.symbol;
+ var base = this.resolve(symbol)
+ , type = base.speciate(id, props);
+ console.log('Added type '+id+':', type);
+ }, this);
+
+ console.groupEnd();
+ this.fire('complete', this);
+ return this;
+ },
+
+ // TODO: repeat other jq events
+ onLoad : function onLoad(data){
+ console.log(this+'.onLoad()');
+ this.process(data);
+ },
+
+ /**
+ * @private
+ */
+ die : function die(reason){
+ throw new Error(this+' Error: '+reason);
+ },
+
+ toString : function(){
+ return this.className+'(file="'+this.path+'")';
+ }
+
+})
+;
--- /dev/null
+//#exports DataFile Loader
+require('Y').Y.core
+.extend(exports, {
+ 'DataFile' : require('ezl/util/data/datafile').DataFile,
+ 'Loader' : require('ezl/util/data/loader').Loader
+});
--- /dev/null
+//#ensure "jquery"
+var Y = require('Y').Y
+, evt = require('evt')
+,
+
+/**
+ * Orchestrates the loading of a number of data files in a civilized fashion,
+ * emitting progress and notifying of completion.
+ */
+Loader =
+exports['Loader'] =
+new evt.Class('Loader', {
+ max : 4,
+
+ queue : [],
+ inFlight : [],
+ running : false,
+
+
+ /**
+ * @param {(Function | Job)[]} [jobs] A list of jobs to enqueue.
+ * @param {Number} [max=4] Maximum concurrent jobs in flight.
+ *
+ * A Job can be a function, or any object with a load() method. In both cases,
+ * it will be called with this Loader as the only argument.
+ *
+ * The job must notify of its completion. It may do so in one of two ways:
+ * - If it implements the {EventDispatcher} interface, the Loader will listen
+ * for the 'complete' event.
+ * - Otherwise, the job must trigger the 'job.complete' event on this Loader,
+ * ensuring the 'target' property of the dispatched event points to the
+ * original job object.
+ *
+ * TODO: support all that crap. Only supporting dispachers for now.
+ */
+ init : function initLoader(jobs, max){
+ this.queue = Y([]);
+ this.inFlight = Y([]);
+ if (jobs) this.addJobs.apply(this, jobs);
+ if (max) this.max = max;
+ },
+
+ /**
+ * Accepts any number of jobs (Functions or objects with a load() method) to load.
+ * @return {this}
+ */
+ addJobs : function addJobs(job){
+ // TODO: validate jobs
+ // Y(arguments).reduce(this.queue.push.genericize().limit(2), this.queue);
+ Y(arguments).forEach(function(job){ this.queue.push(job); }, this);
+ this.nextJob();
+ return this;
+ },
+
+ /**
+ * Starts the load process if stopped.
+ * Has no effect if loading has already commenced.
+ * @return {this}
+ */
+ start : function start(){
+ if (!this.running) {
+ this.running = true;
+ this.fire('start');
+ this.nextJob();
+ }
+ return this;
+ },
+
+ /**
+ * Stops the load process if running.
+ * Has no effect if loader has not started.
+ * @return {this}
+ */
+ stop : function stop(){
+ if (this.running) {
+ this.fire('stop');
+ this.running = false;
+ }
+ return this;
+ },
+
+ /**
+ * @protected
+ */
+ nextJob : function nextJob(){
+ var self = this;
+ // console.log(self+'.nextJob()');
+ if ( !self.running
+ || self.inFlight.length >= self.max
+ || !self.queue.length )
+ return;
+
+ var job = self.queue.shift();
+ job.addEventListener('complete', function onJobComplete(evt){
+ // console.log(self+'.onJobComplete('+job+')');
+ job.removeEventListener('complete', arguments.callee);
+ self.inFlight.remove(job);
+ self.nextJob();
+ });
+ self.inFlight.push(job);
+ job.load(self);
+
+ // Recurse until we've got enough in-flight, or we're out of queued jobs
+ if (!self.queue.length){
+ this.running = false;
+ self.fire('complete');
+ } else
+ self.nextJob();
+ },
+
+ toString : function(){
+ return this.className+'(running='+this.running+', inFlight='+this.inFlight.length+', queue='+this.queue.length+')';
+ }
+
+})
+;
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
-var Y = require('Y').Y
-, Config = require('Y/modules/y.config').Config
-, Vec = require('ezl/math').Vec
+var Y = require('Y').Y
+, Config = require('Y/modules/y.config').Config
+
+, Vec = require('ezl/math').Vec
+, DataFile = require('ezl/util/data/datafile').DataFile
+, Loader = require('ezl/util/data/loader').Loader
,
+/// Config (Inserted Here) ///
+
defaults =
exports['defaults'] = {
game : {
exports['config'] = new Config(defaults)
;
+// Updates the midpoint square when square-size changes
config.addEventListener('set:pathing.gridSquare', function(evt){
var sq = evt.data.newval;
config.set('pathing.gridSquareMid', new Vec(sq/2, sq/2));
});
+
+
+/// Load Data Files ///
+
+exports['loadDataFiles'] =
+ function loadDataFiles(){
+ var files =
+ 'types/buffs types/items types/units' // types/level levels game
+ .split(' ')
+ .map(function(type){
+ return new DataFile('build/data/'+type+'.json');
+ });
+ return new Loader(files).start();
+ };
+
/// Configurable ///
- priority : 0, // Order of stat and effect application (lower is earlier) // TODO
- timeout : 5000,
+ priority : 0, // Order of stat and effect application (lower is earlier) // TODO: doesn't do anything
+ timeout : Infinity,
threat : 1, // TODO
// TODO: functions
triggers : {}, // {event : Effect} // maybe
/// Instance ///
- name : 'Turrrrbo',
+ name : '',
owner : null, // Agent which originated the buff
target : null, // Agent to which the buff applies
game : null,
this.pathmap.addBlocker(unit);
- if ( unit.active && !this.byId[unit.id] ) {
- this.byId[unit.id] = unit;
+ if ( unit.active && !this.byId[unit.__id__] ) {
+ this.byId[unit.__id__] = unit;
this.active.push(unit);
if (unit instanceof Bullet)
if (unit instanceof Event)
unit = unit.trigger;
- delete this.byId[unit.id];
+ delete this.byId[unit.__id__];
this.active.remove(unit);
this.pathmap.removeBlocker(unit);
props = props || {};
props.__species__ = id;
- delete props.id; // reserved for instance id
+ // delete props.id; // reserved for instance id
var cls = this
, speciesName = this.id2name(id)
, map = require('tanks/map/map')
, stat = require('tanks/effects/stat')
, Quantified = require('tanks/mixins/quantified').Quantified
+, Speciated = require('tanks/mixins/speciated').Speciated
,
Thing =
exports['Thing'] =
new evt.Class('Thing', {
- __mixins__ : [ Quantified ],
+ __mixins__ : [ Quantified, Speciated ],
// Config
showAttackCooldown : null,
},
toString : function toString(){
- return this.className+'(id='+this.id+', loc='+this.loc+')';
+ return this.className+'(id='+this.__id__+', loc='+this.loc+')';
}
});
require("Y/modules/y.kv");
var Y = require('Y').Y
+
+, FpsSparkline = require('ezl/loop').FpsSparkline
+
+, cfg = require('tanks/config')
, configui = require('tanks/ui/configui')
, Game = require('tanks/game').Game
, Tank = require('tanks/thing').Tank
-, FpsSparkline = require('ezl/loop').FpsSparkline
-, config = require('tanks/config').config
+, config = cfg.config
, updateTimer = null
, LBT = null
;
// Main method is only executed once, so we'll setup things
// that don't change between games.
function main(){
+
+ // TODO: wait until completed to allow start
+ // TODO: loading screen
+ var configLoader = cfg.loadDataFiles();
+
$('#welcome').center();
/// Debug ///
+//#ensure "jquery"
var Y = require('Y').Y
, Rect = require('ezl/shape').Rect
},
monitorAgent : function monitorAgent(agent){
- if ( agent && !this.hasPath(agent.id) )
+ if ( agent && !this.hasPath(agent.__id__) )
agent.addEventListener('destroy', this.cleanUpAgent);
},
- cleanUpAgent : function cleanUpAgent(evt){ this.destroyPath(evt.target.id); },
+ cleanUpAgent : function cleanUpAgent(evt){ this.destroyPath(evt.target.__id__); },
getPath : function getPath(id){ return $('.path.agent_'+id, this.layer); },
hasPath : function hasPath(id){ return !!this.getPath(id).length; },
drawPath : function drawPath(agent, start, path){
this.monitorAgent(agent);
- var id = agent.id
+ var id = agent.__id__
, w = this.layerWidth, h = this.layerHeight
, canvas = this.getPath(id)
;
<script src="build/tanks/effects/stat.js" type="text/javascript"></script>
<script src="build/tanks/map/map.js" type="text/javascript"></script>
<script src="build/Y/modules/y.cookies.js" type="text/javascript"></script>
+<script src="build/ezl/util/data/datafile.js" type="text/javascript"></script>
+<script src="build/ezl/util/data/loader.js" type="text/javascript"></script>
<script src="build/tanks/mixins/speciated.js" type="text/javascript"></script>
<script src="build/tanks/mixins/meronomic.js" type="text/javascript"></script>
<script src="build/tanks/map/traversal.js" type="text/javascript"></script>
<script src="build/tanks/effects/buff.js" type="text/javascript"></script>
<script src="build/tanks/map/trajectory.js" type="text/javascript"></script>
<script src="build/tanks/effects.js" type="text/javascript"></script>
-<script src="build/tanks/thing/item.js" type="text/javascript"></script>
<script src="build/tanks/ui/pathmapui.js" type="text/javascript"></script>
+<script src="build/tanks/thing/item.js" type="text/javascript"></script>
<script src="build/tanks/thing/wall.js" type="text/javascript"></script>
<script src="build/tanks/ui/grid.js" type="text/javascript"></script>
<script src="build/tanks/fx/explosion.js" type="text/javascript"></script>