Can you make custom spells?

Title says it all. For example in ace of coders, how do you make hero.getControlPoints? In mages might how do they do hero.fire?
I want to make something called revive in my game. Add it to dark alchemist. Revives 1 corpse to full health and speed. The revived corpse is commandable. Do it with hero.command(darkalchemist, ‘revive’, corpse) Is it possible?

Check their referees. Here is what I found in ace-of-coders’s referee:

{
  setUpLevel: ->
    @locationize()
    for hero in [@hero, @hero2]
      hero.autoCollects = false
      hero.addTrackedProperties ['maxHealth', 'number']
      hero.keepTrackedProperty 'maxHealth'
      hero.keepTrackedProperty 'health'
      hero.maxHealth = hero.health = 6000
      hero.maxSpeed = 4
      hero.visualRange = 9001
      hero.visualRangeSquared = 9001 * 9001
      hero.updateRegistration()
      hero.getControlPoints = @getControlPoints
      hero.getControlPointsMap = @getControlPointsMap
      hero.type = 'goliath'  # In case the session wasn't properly configured (default Tharin)
    @humansYak = null
    @ogresYak = null

  onFirstFrame: ->
    for hero in [@hero, @hero2]
      hero.maxSpeed = 4
      hero.visualRange = 9001
      hero.visualRangeSquared = 9001 * 9001
      hero.type = 'goliath'  # In case the session wasn't properly configured (default Tharin)
    @world.getThangByID("Placeholder Soldier").addEffect {name: 'confuse', duration: 0.1, setTo: true, targetProperty: 'isPreloadingMark'}
    @world.getThangByID("Placeholder Soldier 1").addEffect {name: 'paralyze', duration: 0.1, setTo: true, targetProperty: 'isPreloadingMark'}
  locationize: ->
    width = 112
    height = 92
    minX = 4
    minY = 4
    maxX = 116
    maxY = 96
    center = new Vector (minX + maxX) / 2, (minY + maxY) / 2 
    humanCorner = new Vector minX, minY
    ogreCorner = new Vector maxX, maxY
    toHumans = humanCorner.copy().subtract center
    toOgres = ogreCorner.copy().subtract center
    @humansYakPos = center.copy().add(toHumans.copy().multiply(0.6).rotate(-Math.PI / 2))
    @ogresYakPos = center.copy().add(toOgres.copy().multiply(0.6).rotate(-Math.PI / 2))
    @addControlPoint 'Center', center.copy(), null, {humans: 'center', ogres: 'center'}
    @addControlPoint 'Southwest', center.copy().add(toHumans.copy().multiply(0.88)), null, {humans: 'nearCorner', ogres: 'farCorner'}
    @addControlPoint 'Northeast', center.copy().add(toOgres.copy().multiply(0.88)), null, {humans: 'farCorner', ogres: 'nearCorner'}
    @addControlPoint 'South', center.copy().add(toHumans.copy().multiply(0.55).rotate(Math.PI / 4)), null, {humans: 'nearA', ogres: 'farA'}
    @addControlPoint 'North', center.copy().add(toOgres.copy().multiply(0.55).rotate(Math.PI / 4)), null, {humans: 'farA', ogres: 'nearA'}
    @addControlPoint 'West', center.copy().add(toHumans.copy().multiply(0.65).rotate(-Math.PI / 4)), null, {humans: 'nearB', ogres: 'farB'}
    @addControlPoint 'East', center.copy().add(toOgres.copy().multiply(0.65).rotate(-Math.PI / 4)), null, {humans: 'farB', ogres: 'nearB'}
    chest.pos.z = 1.5 for chest in @sprites
    @world.getThangByID("Red Arrow Tower").pos = center.copy().add(toHumans.copy().multiply(0.8))
    @world.getThangByID("Blue Arrow Tower").pos = center.copy().add(toOgres.copy().multiply(0.8))
    @world.getThangByID("Hero Placeholder").pos = center.copy().add(toHumans.copy().multiply(0.5))
    @world.getThangByID("Hero Placeholder 1").pos = center.copy().add(toOgres.copy().multiply(0.5))
  chooseAction: ->
    @updateShells()
    @autonomizeTowers()
    @maybeSpawnYaks()
    @controlYaks()
    @keepHeroesInBounds()
    @inspireTerror()

  updateShells: ->
    # Normally artillery shoot every 3.6 seconds and shells take 3.4 seconds, but because of 0.25s dt, they shoot again before their targets are defeated. Fix it.
    gravity = @world.getSystem("Movement").gravity
    for shell in @world.thangs when shell.type is 'shell' and not shell.lifespanExtended
      shell.lifespanExtended = true
      shell.lifespan += 1
      for ticks in [0 ... 3]
        shell.velocity.z -= gravity * @world.dt
        for t in ['x', 'y', 'z']
          shell.pos[t] += shell.velocity[t] * @world.dt
  autonomizeTowers: ->
    # Make them auto-attack by removing their desire to wait for their commander's orders.
    for tower in @world.thangs when tower.type is 'arrow-tower' and tower.commander
      tower.commander = null

  spawnHumansYak: ->
    return if @humansYak?.health > 0
    @humansYak = @instabuild 'ice-yak', @humansYakPos.x, @humansYakPos.y

  spawnOgresYak: ->
    return if @ogresYak?.health > 0
    @ogresYak = @instabuild 'ice-yak', @ogresYakPos.x, @ogresYakPos.y


  controlYaks: ->
    @humansYak?.attack @hero
    @ogresYak?.attack @hero2
    @humansYak?.lastAttacker = @hero
    @ogresYak?.lastAttacker = @hero2

  maybeSpawnYaks: ->
    if @world.age > 150
      @spawnHumansYak()
      @spawnOgresYak()
      return
    @spawnHumansYak() if @hero.errorsOut
    @spawnOgresYak() if @hero2.errorsOut

  keepHeroesInBounds: ->
    for hero in [@hero, @hero2]
      @boundsRect ?= new Rectangle(60, 50, 112, 92)
      unless @boundsRect.containsPoint hero.pos
        hero.pos = @boundsRect.getPos()
  inspireTerror: ->
    return unless @hero.dead or @hero2.dead
    dead = if @hero.dead then @hero else @hero2
    for t in @world.thangs when t.team is @loser and t.health > 0 and t.move
      #enemy = t.findNearestEnemy()
      #t.move t.pos.copy().add(t.pos.copy().subtract(enemy.pos).normalize().multiply(100))
      #t.move t.findNearest([@humansYakPos, @ogresYakPos])
      t._retreatTo ?= new Vector(1000, 0).rotate @world.rand.randf() * Math.PI * 2
      t.move t._retreatTo
      t.say @icontext.no

  declareVictory: (team) ->
    console.log 'Declaring victory for', team, 'at time', @world.age
    @loser = otherTeam = if team is 'humans' then 'ogres' else 'humans'
    @world.setGoalState "destroy-#{otherTeam}", 'success'
    @world.setGoalState "save-#{team}", 'success'
    @world.setGoalState "save-#{otherTeam}", 'failure'
    @world.endWorld true, 3
    @victory = true
    return unless @hero.dead or @hero2.dead
    victor = if team is 'humans' then @hero else @hero2
    victor._takeDamage = victor.takeDamage
    victor.takeDamage = (damage, args...) -> @_takeDamage 0, args...
    t.maxSpeed *= 1.5 for t in @world.thangs when t.team is otherTeam and t.maxSpeed and t.health > 0
  checkVictory: ->
    if @hero.dead and not @hero2.dead
      @declareVictory 'ogres'
    else if @hero2.dead and not @hero.dead
      @declareVictory 'humans'
    else if (@hero.dead and @hero2.dead) or @world.frames.length is @world.totalFrames - 2
      # Break ties by remaining army power, then health, then gold remaining, then gold collected, then at random.
      for tiebreakScorer, i in [
          ((team) => @powerForTeam(team))
          ((team) => _.reduce (t.health for t in @world.thangs when t.team is team and t.health > 0), ((sum, num) -> sum + num), 0)
          ((team) => @world.getSystem("Inventory").teamGold[team].gold)
          ((team) => @world.getSystem("Inventory").teamGold[team].earned)
          ((team) => @world.rand.randf())
          ]
        humansScore = tiebreakScorer 'humans'
        ogresScore = tiebreakScorer 'ogres'
        if humansScore > ogresScore
          @declareVictory 'humans'
          break
        else if humansScore < ogresScore
          @declareVictory 'ogres'
          break
        else if humansScore is ogresScore
          console.log 'Breaking tie failed with scorer', i, 'because scores are both', humansScore
        else
          console.log 'Tiebreaker fail!', i, humansScore, ogresScore
  powerForTeam: (team) ->
    @costTable ?=
      soldier: 20
      archer: 25
      artillery: 75
      'arrow-tower': 100
    power = 0
    for thang in @world.thangs when thang.health > 0 and thang.team is team
      power += @costTable[thang.type] or 0
    power

Everything is from the referee. All credits for this to the level and it’s creators.

1 Like