Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Creature Spawning #715

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions data/conf/zones.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
maxOccurs="unbounded"/>
<element name="associated" type="Q1:associatedZones" minOccurs="0"
maxOccurs="1"/>
<element name="spawns" type="Q1:spawnsGroup" minOccurs="0" maxOccurs="1"/>
</choice>
</sequence>
<attribute name="name" type="string"/>
Expand Down Expand Up @@ -178,4 +179,27 @@
<attribute name="zones" type="string" use="required"></attribute>
</complexType>

<complexType name="spawnsGroup">
<choice minOccurs="1" maxOccurs="unbounded">
<element name="creatures" type="Q1:creaturesSpawnsGroup" minOccurs="0" maxOccurs="1"/>
</choice>
</complexType>

<!--
adding a dynamic creature spawner to zone:
<spawns>
<creatures>
<parameter name="bat">15</parameter>
</creatures>
</spans>

"name" attribute is the creature to be spawned, value is the maximum number that can be active
at same time
-->
<complexType name="creaturesSpawnsGroup">
<sequence>
<element name="parameter" type="Q1:parameterType" minOccurs="1" maxOccurs="unbounded"/>
</sequence>
</complexType>

</schema>
1 change: 1 addition & 0 deletions doc/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Changelog
- new achievements:
- 30 Minutes or Less: Deliver 25 hot pizzas
- Balduin allows requesting different item for Ultimate Collector quest
- support added for dynamic creature spawning

*web client*
- fixed text extending past edge of notification bubbles
Expand Down
4 changes: 2 additions & 2 deletions src/games/stendhal/client/entity/factory/EntityMap.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/***************************************************************************
* (C) Copyright 2003-2023 - Marauroa *
* (C) Copyright 2003-2024 - Marauroa *
***************************************************************************
***************************************************************************
* *
Expand All @@ -17,7 +17,6 @@

import org.apache.log4j.Logger;

import games.stendhal.client.Triple;
import games.stendhal.client.entity.Block;
import games.stendhal.client.entity.Blood;
import games.stendhal.client.entity.BossCreature;
Expand Down Expand Up @@ -53,6 +52,7 @@
import games.stendhal.client.entity.UseableRing;
import games.stendhal.client.entity.WalkBlocker;
import games.stendhal.client.entity.Wall;
import games.stendhal.common.Triple;

/**
* Registers the relationship between Type, eclass and java class of entity
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2023 - Stendhal *
* (C) Copyright 2003-2024 - Stendhal *
***************************************************************************
***************************************************************************
* *
Expand All @@ -17,9 +17,9 @@

import org.apache.log4j.Logger;

import games.stendhal.client.Triple;
import games.stendhal.client.entity.IEntity;
import games.stendhal.client.gui.wt.core.WtWindowManager;
import games.stendhal.common.Triple;

/*
* The entity views are generic, but we don't simply have sufficient data to
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2023 - Stendhal *
* (C) Copyright 2003-2024 - Stendhal *
***************************************************************************
***************************************************************************
* *
Expand All @@ -10,7 +10,7 @@
* (at your option) any later version. *
* *
***************************************************************************/
package games.stendhal.client;
package games.stendhal.common;

import java.util.Objects;

Expand Down
10 changes: 10 additions & 0 deletions src/games/stendhal/server/core/config/ZonesXMLLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import games.stendhal.common.tiled.StendhalMapStructure;
import games.stendhal.server.core.config.zone.AttributesXMLReader;
import games.stendhal.server.core.config.zone.ConfiguratorXMLReader;
import games.stendhal.server.core.config.zone.CreatureSpawnsXMLReader;
import games.stendhal.server.core.config.zone.EntitySetupXMLReader;
import games.stendhal.server.core.config.zone.PortalSetupXMLReader;
import games.stendhal.server.core.config.zone.RegionNameSubstitutionHelper;
Expand All @@ -57,6 +58,8 @@ public final class ZonesXMLLoader {

/** Zone attributes reader. */
private static final SetupXMLReader attributesReader = new AttributesXMLReader();
/** Dynamically spawned creatures. */
private static final SetupXMLReader creatureSpawnsReader = new CreatureSpawnsXMLReader();
/**
* The ConfiguratorDescriptor XML reader.
*/
Expand Down Expand Up @@ -416,6 +419,13 @@ public ZoneDesc readZone(final Element element) {
continue;
} else if (tag.equals("associated")) {
desc.setAssociatedZones(child.getAttribute("zones"));
} else if (tag.equals("spawns")) {
for (final Element spawn: XMLUtil.getElements(child)) {
final String spawnType = spawn.getTagName();
if ("creatures".equals(spawnType)) {
setupDesc = creatureSpawnsReader.read(spawn);
}
}
} else {
logger.warn("Zone [" + name + "] has unknown element: " + tag);
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/***************************************************************************
* Copyright © 2024 - Faiumoni e. V. *
***************************************************************************
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
package games.stendhal.server.core.config.zone;

import java.util.Map;

import org.apache.log4j.Logger;
import org.w3c.dom.Element;

import games.stendhal.common.MathHelper;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.entity.mapstuff.spawner.DynamicCreatureSpawner;


/**
* Parses info for dynamically spawned creatures.
*/
public class CreatureSpawnsXMLReader extends SetupXMLReader {

private static Logger logger = Logger.getLogger(CreatureSpawnsXMLReader.class);


@Override
public SetupDescriptor read(final Element element) {
final CreatureSpawnsDescriptor desc = new CreatureSpawnsDescriptor();
readParameters(desc, element);
return desc;
}

private static class CreatureSpawnsDescriptor extends SetupDescriptor {
@Override
public void setup(final StendhalRPZone zone) {
final DynamicCreatureSpawner spawner = new DynamicCreatureSpawner(zone);
final Map<String, String> params = getParameters();
for (final String name: params.keySet()) {
final int max = MathHelper.parseIntDefault(params.get(name), 0);
if (max < 0) {
logger.warn("Max must be a positive integer, not registering dynamic spawn for creature \"" + name + "\"");
continue;
}
spawner.register(name, max);
}
// NOTE: does zone need to know about dynamic spawner?
zone.setDynamicSpawner(spawner);
}
}
}
30 changes: 30 additions & 0 deletions src/games/stendhal/server/core/engine/StendhalRPZone.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import games.stendhal.server.entity.mapstuff.portal.OneWayPortalDestination;
import games.stendhal.server.entity.mapstuff.portal.Portal;
import games.stendhal.server.entity.mapstuff.spawner.CreatureRespawnPoint;
import games.stendhal.server.entity.mapstuff.spawner.DynamicCreatureSpawner;
import games.stendhal.server.entity.mapstuff.spawner.PassiveEntityRespawnPoint;
import games.stendhal.server.entity.mapstuff.spawner.PassiveEntityRespawnPointFactory;
import games.stendhal.server.entity.mapstuff.spawner.SheepFood;
Expand Down Expand Up @@ -114,8 +115,12 @@ public class StendhalRPZone extends MarauroaRPZone {
*/
private final List<SheepFood> sheepFoods;

/** Statically spawned creatures. */
private final List<CreatureRespawnPoint> respawnPoints;

/** Dynamically spawned creatures. */
private DynamicCreatureSpawner dynamicSpawner;

private final List<PassiveEntityRespawnPoint> plantGrowers;

private final List<RPEntity> playersAndFriends;
Expand Down Expand Up @@ -312,6 +317,30 @@ public void remove(final CreatureRespawnPoint point) {
respawnPoints.remove(point);
}

/**
* Retrieves the dynamic creature spawner associated with this zone.
*
* @return
* Creature spawner instance.
*/
public DynamicCreatureSpawner getDynamicSpawner() {
return dynamicSpawner;
}

/**
* Sets the dynamic creature spawner associated with this zone.
*
* @param spawner
* Creature spawner instance.
*/
public void setDynamicSpawner(final DynamicCreatureSpawner spawner) {
if (dynamicSpawner != null) {
dynamicSpawner.remove();
}
dynamicSpawner = spawner;
dynamicSpawner.init();
}

/**
* Retrieves growers in this zone.
*/
Expand Down Expand Up @@ -1534,6 +1563,7 @@ public void nextTurn() {
os.append("playersAndFriends: " + playersAndFriends.size() + "\n");
os.append("portals: " + portals.size() + "\n");
os.append("respawnPoints: " + respawnPoints.size() + "\n");
os.append("dynamicRespawns: " + dynamicSpawner.maxTotal() + "\n");
os.append("sheepFoods: " + sheepFoods.size() + "\n");
os.append("objects: " + objects.size() + "\n");
logger.info(os);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
* (C) Copyright 2003-2013 - Marauroa *
* (C) Copyright 2003-2024 - Marauroa *
***************************************************************************
***************************************************************************
* *
Expand Down Expand Up @@ -382,6 +382,16 @@ public int compare(final DropItem o1, final DropItem o2) {
return creature;
}

/**
* Creates a creature instance with randomized stats.
*
* @return
* New creature instance.
*/
public Creature getCreatureRandomizeStats() {
return getCreature().getNewInstanceRandomizeStats();
}

/** @return the tileid. */
public String getTileId() {
return tileid;
Expand Down
57 changes: 49 additions & 8 deletions src/games/stendhal/server/entity/creature/Creature.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import games.stendhal.common.Rand;
import games.stendhal.common.constants.Nature;
import games.stendhal.common.constants.SoundLayer;
import games.stendhal.common.constants.Testing;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPRuleProcessor;
import games.stendhal.server.core.engine.StendhalRPZone;
Expand All @@ -49,7 +50,7 @@
import games.stendhal.server.entity.item.Corpse;
import games.stendhal.server.entity.item.Item;
import games.stendhal.server.entity.item.StackableItem;
import games.stendhal.server.entity.mapstuff.spawner.CreatureRespawnPoint;
import games.stendhal.server.entity.mapstuff.spawner.CreatureSpawner;
import games.stendhal.server.entity.npc.NPC;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.entity.slot.EntitySlot;
Expand Down Expand Up @@ -122,7 +123,7 @@ public class Creature extends NPC {
private int corpseWidth = 1;
private int corpseHeight = 1;

private CreatureRespawnPoint point;
private CreatureSpawner spawner;

/** Respawn time in turns */
private int respawnTime;
Expand Down Expand Up @@ -368,6 +369,24 @@ public Creature getNewInstance() {
return new Creature(this);
}

/**
* Creates a new creature with randomized stats using this instance as a template.
*
* @return
* New creature instance.
*/
public Creature getNewInstanceRandomizeStats() {
final Creature newInstance = getNewInstance();
// A bit of randomization to make Joan and Snaketails a bit happier.
// :)
newInstance.setAtk(Rand.randGaussian(newInstance.getAtk(), newInstance.getAtk() / 10));
newInstance.setDef(Rand.randGaussian(newInstance.getDef(), newInstance.getDef() / 10));
if (Testing.COMBAT) {
newInstance.setRatk(Rand.randGaussian(newInstance.getRatk(), newInstance.getRatk() / 10));
}
return newInstance;
}

/**
* Sets the sound played at creature's death
*
Expand Down Expand Up @@ -528,18 +547,40 @@ public Map<String, String> getAIProfiles() {
return aiProfiles;
}

public void setRespawnPoint(final CreatureRespawnPoint point) {
this.point = point;
/**
* Registers this creature with a spawner.
*
* @param spawner
* Spawner instance.
*/
public void setSpawner(final CreatureSpawner spawner) {
this.spawner = spawner;
setRespawned(true);
}

@Deprecated
public void setRespawnPoint(final CreatureSpawner spawner) {
setSpawner(spawner);
}

/**
* Gets the spawner of this creature.
*
* @return
* Spawner instance.
*/
public CreatureSpawner getRespawner() {
return spawner;
}

/**
* gets the respan point of this create
*
* @return CreatureRespawnPoint
*/
public CreatureRespawnPoint getRespawnPoint() {
return point;
@Deprecated
public CreatureSpawner getRespawnPoint() {
return getRespawner();
}

/**
Expand Down Expand Up @@ -664,8 +705,8 @@ public void onDead(final Killer killer, final boolean remove) {

notifyRegisteredObjects();

if (this.point != null) {
this.point.notifyDead(this);
if (this.spawner != null) {
this.spawner.onRemoved(this);
}

super.onDead(killer, remove);
Expand Down
Loading
Loading