/* * Aphelion * Copyright (c) 2014 Joris van der Wel * * This file is part of Aphelion * * Aphelion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * Aphelion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Aphelion. If not, see <http://www.gnu.org/licenses/>. * * In addition, the following supplemental terms apply, based on section 7 of * the GNU Affero General Public License (version 3): * a) Preservation of all legal notices and author attributions * b) Prohibition of misrepresentation of the origin of this material, and * modified versions are required to be marked in reasonable ways as * different from the original version * * Linking this library statically or dynamically with other modules is making a * combined work based on this library. Thus, the terms and conditions of the * GNU Affero General Public License cover the whole combination. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent modules, * and to copy and distribute the resulting executable under terms of your * choice, provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. An independent * module is a module which is not derived from or based on this library. */ package aphelion.server.game; import aphelion.shared.event.TickEvent; import aphelion.shared.net.game.NetworkedActor; import aphelion.shared.net.protobuf.GameOperation; import aphelion.shared.net.protobuf.GameS2C; import aphelion.shared.physics.PhysicsEnvironment; import aphelion.shared.physics.WEAPON_SLOT; import aphelion.shared.physics.valueobjects.PhysicsMovement; import aphelion.shared.swissarmyknife.SwissArmyKnife; import com.google.protobuf.ByteString; import java.util.Collections; /** * * @author Joris */ public class Dummies implements TickEvent { private static final int DUMMIES_PID_START = -10000; private static final int GROUPS = 20; private final PhysicsEnvironment env; private final ServerGame serverGame; private final DummyData[] dummies; private final int GROUP_SIZE; private int index = 0; public Dummies(int DUMMIES, PhysicsEnvironment env, ServerGame serverGame) { this.env = env; this.serverGame = serverGame; dummies = new DummyData[DUMMIES]; GROUP_SIZE = (int) Math.ceil(dummies.length / (double) GROUPS); } public void setup() { for (int p = 0; p < dummies.length; ++p) { DummyData dummy = new DummyData(DUMMIES_PID_START + p, "Dummy " + p); dummies[p] = dummy; dummy.spawn(); } } @Override public void tick(long tick) { final int end = Math.min(index + GROUP_SIZE, dummies.length); for (; index < end; ++index) { DummyData dummy = dummies[index]; if (dummy == null) { continue; } dummy.pickAction(); dummy.doAction(GROUP_SIZE); } if (index == dummies.length) { index = 0; } } private class DummyData { final int pid; final String name; DUMMY_ACTION action = DUMMY_ACTION.NOTHING; PhysicsMovement move = PhysicsMovement.NONE; final int FIRE_DELAY = 5; DummyData(int pid, String name) { this.pid = pid; this.name = name; } void spawn() { env.actorNew(env.getTick() + 1, pid, SwissArmyKnife.random.nextLong(), "superspider"); serverGame.addActor(new NetworkedActor(pid, true, name)); // physics will start of with a random(ish) spawn location } void pickAction() { float rnd = SwissArmyKnife.random.nextFloat(); if (rnd < DUMMY_ACTION.CHANGE_ACTION_CHANCE) { rnd = SwissArmyKnife.random.nextFloat(); for (DUMMY_ACTION possibleAction : DUMMY_ACTION.values) { if (rnd < possibleAction.chance) { this.action = possibleAction; break; } rnd -= possibleAction.chance; } } rnd -= DUMMY_ACTION.CHANGE_ACTION_CHANCE; if (rnd < DUMMY_ACTION.CHANGE_MOVE_CHANCE) { move = PhysicsMovement.get(SwissArmyKnife.random.nextInt(16)); } } void doAction(int tickAmount) { GameS2C.S2C.Builder s2c = GameS2C.S2C.newBuilder(); long tick = env.getTick() - tickAmount; switch (action) { case NOTHING: break; case FIRE: for (long t = tick; t < tick + tickAmount; ++t) { if (t % FIRE_DELAY == 0) { fire(tick + t, s2c); } } break; case MOVE: move(tick, s2c, tickAmount); break; case FIRE_MOVE: move(tick, s2c, tickAmount); for (long t = tick; t < tick + tickAmount; ++t) { if (t % FIRE_DELAY == 0) { fire(tick + t, s2c); } } break; } serverGame.broadcast(s2c); } private void move(long tick, GameS2C.S2C.Builder s2c, int tickAmount) { GameOperation.ActorMove.Builder moveBuilder = s2c.addActorMoveBuilder(); moveBuilder.setTick(tick); moveBuilder.setPid(pid); moveBuilder.setDirect(true); for (long t = tick; t < tick + tickAmount; ++t) { env.actorMove(t, pid, move); } moveBuilder.setMove(ByteString.copyFrom(PhysicsMovement.serializeListLE(Collections.nCopies(tickAmount, move)))); } private void fire(long tick, GameS2C.S2C.Builder s2c) { env.actorWeapon(tick, pid, WEAPON_SLOT.GUN, false, 0, 0, 0, 0, 0); GameOperation.ActorWeapon.Builder weaponBuilder = s2c.addActorWeaponBuilder(); weaponBuilder.setTick(tick); weaponBuilder.setPid(pid); weaponBuilder.setSlot(WEAPON_SLOT.GUN.id); } } private static enum DUMMY_ACTION { // All chances should add up to 1 (at most) NOTHING (0.05f), FIRE (0.05f), MOVE (0.6f), FIRE_MOVE (0.3f); static final float CHANGE_ACTION_CHANCE = 0.2f; static final float CHANGE_MOVE_CHANCE = 0.01f; float chance; static final DUMMY_ACTION[] values = values(); private DUMMY_ACTION(float chance) { this.chance = chance; } } }