1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
#This file is distributed under the terms of the GNU General Public license.
#Copyright (C) 2005-2006 Al Riddoch (See the file COPYING for details).
from atlas import *
from physics import *
from Quaternion import Quaternion
from Vector3D import Vector3D
try:
from random import *
except ImportError:
from whrandom import *
from cyphesis.Thing import Thing
import server
class Combat(Thing):
"""A very simple combat system example."""
def attack_operation(self, op):
""" The attack op is FROM the the character that initiated combat which
we term the attacker, TO the character that is attacker which we
term the defender. We store the IDs of both. """
# Check if the attacked characters stamina is too low for combat
if self.character.stamina < 0.1:
# print "Aborting defender stamina low"
self.irrelevant()
return
assert(op.from_ != op.to)
if op.to != self.character.id:
self.oponent = op.to
# print "Attack operation is not to this character"
# We have initiative
else:
self.oponent = op.from_
# print "Attack operation is to this character"
self.surprise = True
# We do not have initiative
# Attach this task to the attacker. Its already implicitly attached
# to the defender who owns this task.
a=server.world.get_object(self.oponent)
# Check if the attacking characters stamina is too low for combat
if not a or a.stamina < 0.1:
self.irrelevant()
return
# a.set_task(self.cppthing)
self.square_range = 25
def tick_operation(self, op):
""" This method is called repeatedly, each time a combat turn occurs.
In this example the interval is fixed, but it can be varied.
self.attacker is the ID of the character that initiated the combat
self.defender is the ID of the character that was initially
attacked The self.attack flag is used to alternate the attack from
one combatant to the other. """
# if self.count() < 2:
# print "Someone has dropped out"
# self.irrelevant()
# return
assert(self.character.id == op.to)
if self.character.stamina <= 0:
# print "I am exhausted"
self.irrelevant()
return
attacker = self.character
if not attacker:
sys.stderr.write("Attacker owning combat task destroyed, but task still running")
self.irrelevant()
return
if attacker.stamina <= 0:
# print "Attacker exhausted"
self.irrelevant()
return
defender = server.world.get_object(self.oponent)
if not defender:
# print "No defender"
self.irrelevant()
return
if hasattr(self, 'surprise') and self.surprise:
# print 'Surprised!'
self.surprise = False
return self.next_tick(0.75 + uniform(0,0.25))
if square_distance(self.character.location, defender.location) > self.square_range:
return self.next_tick(1.75 + uniform(0,0.25))
a=self.character.id
d=self.oponent
# A very simple formula is used to determine the damage done
damage = (attacker.statistics.attack / defender.statistics.defence) / uniform(2,10)
# Damage is counted against stamina, to ensure combat is non lethal,
# and make recovery easier.
stamina=defender.stamina-damage
if stamina<0: stamina=0
set_arg=Entity(self.oponent, stamina=stamina)
# We send 3 operations to indicate what is going on. The imginary ops
# provide emotes for the actions. The sight(attack) operation
# indicates that a singleshot animation of attacking should be
# triggered on the attacker.
attacker.send_world(Operation("imaginary", Entity(description="hits for massive damage."), to=attacker))
attacker.send_world(Operation("sight", Operation("attack", to=d, from_=a)))
defender.send_world(Operation("imaginary", Entity(description="defends skillfully."), to=defender))
# If the defenders stamina has reached zero, combat is over, and emotes
# are sent to indicate this.
if stamina <= 0:
set_arg.status = defender.status - 0.1
defender.send_world(Operation("imaginary", Entity(description="has been defeated"), to=defender))
defender.send_world(Operation("sight", Operation("collapse", from_=d)))
attacker.send_world(Operation("imaginary", Entity(description="is victorious"), to=attacker))
self.irrelevant()
res=Message()
# This set op makes the change to defenders stamina, and a small health
# change if they have been defeated
res.append(Operation("set", set_arg, to=defender))
# Turn the attacker to face the defender. This has to go through
# the mind2body interface, so it does not interrupt what the
# the character is doing.
faceop=self.face(defender)
if faceop:
faceop=attacker.mind2body(faceop)
if faceop:
res.append(faceop)
# Don't return the following tick op if this task is now complete
if self.obsolete():
return res
# Schedule a new tick op
res.append(self.next_tick(1.75 + uniform(0,0.25)))
return res
def face(self, other):
""" Turn to face that another character, ensuring that
we are facing the character we are hitting """
vector = distance_to(self.character.location, other.location)
vector.z = 0
if vector.square_mag() < 0.1:
return
vector = vector.unit_vector()
newloc = Location(self.character.location.parent)
newloc.orientation = Quaternion(Vector3D(1,0,0), vector)
return Operation("move", Entity(self.character.id, location=newloc))
|