cyphesis/rulesets/deeds/world/tasks/Combat.py
Erik Ogenvik 0572fd6d4a Use "props" to get properties.
Also unify LocatedEntity Python bindings.
2018-05-14 17:35:19 +02:00

146 lines
5.9 KiB
Python

#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 physics import Quaternion
from physics import Vector3D
from random import *
import server
class Combat(server.Task):
"""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.props.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.props.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.props.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.props.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.props.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=Oplist()
# 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.y = 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))