mirror of
https://github.com/worldforge/cyphesis
synced 2026-08-13 12:26:04 -04:00
146 lines
5.9 KiB
Python
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))
|