mirror of
https://github.com/worldforge/cyphesis
synced 2026-08-13 12:26:04 -04:00
It's currently a copy of the Mason ruleset. Where Mason focuses on letting the users create the world themselves, Deeds instead focuses on allowing world authors to set up enticing worlds with gameplay and stories. In contrast to Mason there are more features available to the world authors which allow for automatic creation of entities and so on.
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.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=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.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))
|