/*
* Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
*
* Copyright (c) 2011 - 2014 Michael Andonie and contributors.
*
* 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 3 of the License, or
* any later version.
*
* This program 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ea;
import ea.internal.collision.Collider;
import ea.internal.util.Logger;
import java.awt.*;
import java.util.ArrayList;
/**
* Eine Actionfigur ist eine besondere Figur. Diese hat verschiedene <b>Zustände</b> und kann
* verschiedene <b>Aktionen</b> durchführen. Das bedeutet: Jeder <b>Zustand</b> und jede
* <b>Aktion</b> werden von einem eigenen <code>Figur</code>-Objekt dargestellt.
* <p/>
* Die Figur des aktuellen <b>Zustandes</b> wird normalerweise dargestellt.
* <p/>
* Wird eine <b>Aktion</b> ausgeführt, so wird die dazugehoerige Figur einmal durchanimiert.
* Anschliessend kehrt die Figur in ihren <b>Zustand</b> zurück.
* <p/>
* <b>WICHTIG</b>:<br /> Damit eine Actionfigur <i>immer</i> ordnungsgemäß funktioniert
* (Spiegelungen), sollten alle Figuren die selben Masße ("Pixel"-Hoehe/-Breite) haben.
* <p/>
* Es müssen nicht alle Felder ausgefüllt sein, damit Kollisionstests etc. immer funktionieren. Es
* sei denn, die rechenintensive Arbeit wurde durch die Klasse <code>Game</code> ausgeschaltet
* ({@link ea.Game#rechenintensiveArbeitSetzen(boolean)}).
*
* @author Michael Andonie
*/
public class ActionFigur extends Raum {
/**
* Eine Liste mit allen Figuren (um die Animationsschritte zu machen).
*/
private static final ArrayList<ActionFigur> FIGUREN;
static {
FIGUREN = new ArrayList<>();
// FIXME: 1 ms ist ein zu kleines Intervall und sorgt für die Performance-Schwierigkeiten bei vielen Figuren
// Richtige Time nutzen? Wie schnell sind Time-Aufrufe in Java?
Manager.standard.anmelden(new Ticker() {
int runde = 0;
public void tick () {
runde++;
try {
for (ActionFigur f : FIGUREN) {
f.animationsSchritt(runde);
}
} catch (Exception e) {
// don't care (ConcurrentModification and NullPointer)
}
}
}, 1);
}
/**
* Die Sammlung aller Zustände
*/
private Figur[] states;
/**
* Die Namen der Zustände
*/
private String[] stateNames;
/**
* Die Sammlung aller Aktionen
*/
private Figur[] actions;
/**
* Die Namen der Aktionen
*/
private String[] actionNames;
/**
* Der Index des aktuellen Zustandes
*/
private int indexState = 0;
/**
* Der Index der zuletzt auszuführenden Aktion
*/
private int indexAction = 0;
/**
* <code>true</code>, solange eine Aktion auszuführen ist
*/
private boolean performsAction = false;
/**
* Konstruktor.
*
* @param zustand
* Der erste Zustand der Figur. Weitere Zustände können über die Methode {@link
* #neuerZustand(Figur, String)} angemeldet werden.
* @param name
* Der Name des ersten Zustandes. Weitere Zustände können über die Methode {@link
* #neuerZustand(Figur, String)} angemeldet werden.
* <p/>
* <b>Beim Namen wird die Groß- / Kleinschreibung ignoriert.</b>
*/
public ActionFigur (Figur zustand, String name) {
this.states = new Figur[0];
this.stateNames = new String[0];
this.actions = new Figur[0];
this.actionNames = new String[0];
neuerZustand(zustand, name);
FIGUREN.add(this);
}
/**
* Meldet einen neuen Zustand für diese Figur an.
*
* @param zustand
* Die Figur, die diesen Zustand beschreibt.
* @param name
* Der Name, unter dem dieser Zustand aufgerufen wird.
* <p/>
* <b>Beim Namen wird die Groß- / Kleinschreibung ignoriert.</b>
*
* @see #neueAktion(Figur, String)
*/
@API
public void neuerZustand (Figur zustand, String name) {
zustand.entfernen();
if (this.states.length > 0) {
zustand.positionSetzen(aktuelleFigur().position());
}
Figur[] statesBefore = this.states;
String[] stateNamesBefore = this.stateNames;
this.states = new Figur[statesBefore.length + 1];
this.stateNames = new String[stateNamesBefore.length + 1];
for (int i = 0; i < statesBefore.length; i++) {
this.states[i] = statesBefore[i];
this.stateNames[i] = stateNamesBefore[i];
}
this.states[this.states.length - 1] = zustand;
this.stateNames[this.stateNames.length - 1] = name.toLowerCase();
}
/**
* Gibt die aktuelle Figur zurück.
*
* @return Die Figur, die gerade von dieser ActionFigur zu sehen ist.
*/
public Figur aktuelleFigur () {
return performsAction ? actions[indexAction] : states[indexState];
}
/**
* Meldet eine neue Aktion für diese Figur an.
*
* @param action
* Die Figur, die diese Aktion beschreibt.
* @param name
* Der Name, unter dem diese Aktion aufgerufen wird.
* <p/>
* <b>Beim Namen wird die Groß- / Kleinschreibung ignoriert.</b>
*
* @see #neuerZustand(Figur, String)
*/
@API
@SuppressWarnings ( "unused" )
public void neueAktion (Figur action, String name) {
action.entfernen();
action.positionSetzen(aktuelleFigur().position());
Figur[] actionsBefore = actions;
String[] actionNamesBefore = actionNames;
actions = new Figur[actionsBefore.length + 1];
actionNames = new String[actionNamesBefore.length + 1];
for (int i = 0; i < actionsBefore.length; i++) {
actions[i] = actionsBefore[i];
actionNames[i] = actionNamesBefore[i];
}
actions[actions.length - 1] = action;
actionNames[actionNames.length - 1] = name.toLowerCase();
}
/**
* Versetzt diese Actionfigur in einen bestimmten Zustand.
* <p/>
* <i>Vollführt die Figur jedoch gerade eine <b>Aktion</b>, so ist der neue Zustand erst danach
* sichtbar.</i>
*
* @param name
* Der Name des Zustandes, in den die Figur versetzt werden soll. Dies ist der Name, der beim
* Anmelden des Zustandes mitgegeben wurde.
* <p/>
* <b>Beim Namen wird die Groß- / Kleinschreibung ignoriert.</b>
*
* @see #aktionSetzen(String)
*/
@API
@SuppressWarnings ( "unused" )
public void zustandSetzen (String name) {
name = name.toLowerCase();
for (int i = 0; i < stateNames.length; i++) {
if (stateNames[i].equals(name)) {
indexState = i;
return;
}
}
Logger.error("Achtung! Der Name des auszufuehrenden Zustandes wurde nie bei einer Anmeldung mitgegeben! " +
"Der Name, der nicht unter den Zustaenden gefunden wurde war: " + name);
}
/**
* Versetzt diese Actionfigur in eine bestimmte Aktion.
*
* @param name
* Der Name der Aktion, die die Figur ausführen soll. Dies ist der Name, der beim Anmelden der
* Aktion mitgegeben wurde.
* <p/>
* <b>Beim Namen wird die Groß- / Kleinschreibung ignoriert.</b>
*
* @see #zustandSetzen(String)
*/
@API
@SuppressWarnings ( "unused" )
public void aktionSetzen (String name) {
name = name.toLowerCase();
for (int i = 0; i < actionNames.length; i++) {
if (actionNames[i].equals(name)) {
hatAktionSetzen(true);
indexAction = i;
return;
}
}
Logger.error("Achtung! Der Name der auszufuehrenden Aktion wurde nie bei einer Anmeldung mitgegeben! " +
"Der Name, der nicht unter den angemeldeten Aktionen gefunden wurde war: " + name);
}
/**
* Setzt, ob diese Figur zur Zeit eine Aktion hat.
* <p/>
* Diese Methode sollte <b>nicht</b> von außen aktiviert werden. Hierfuer gibt es:<br /> {@link
* #zustandSetzen(String)} und {@link #aktionSetzen(String)}.
*
* @param action
* Ob diese Figur gerade eine Aktion ausführt.
*/
@NoExternalUse
public void hatAktionSetzen (boolean action) { // TODO auf private setzen?
if (performsAction) {
actions[indexAction].animationsBildSetzen(0);
}
performsAction = action;
}
/**
* Gibt den aktuellen Zustand dieser Action-Figur als <code>String</code> aus.
*
* @return Der Name des aktuellen Zustandes. Ist die Figur zur Zeit in einem <b>Zustand</b>, so
* ist dies der Name des aktuellen Zustandes, vollführt die Figur zur Zeit eine <b>Aktion</b>,
* so ist dies der Name der aktuellen Aktion.
*/
@API
@SuppressWarnings ( "unused" )
public String aktuellesVerhalten () {
return performsAction ? actionNames[indexAction] : stateNames[indexState];
}
/**
* Spiegelt <b><u>alle</u> Figuren der <i>Zustände</i> und <i>Aktionen</i></b> dieser Figur an
* der X-Achse.
* <p/>
* <b>ACHTUNG!</b>
* <p/>
* Damit diese Methode <b>nicht zu ungewollten Missverständnissen</b> führt, sollte folgendes
* beachtet werden:<br /> <i>Sämtliche Figuren, die an dieser Action-Figur angemeldet sind,
* sollten <b>dieselben Maße haben</b>. Sie sollten also alle aus derselben Anzahl an
* "Unterquadraten" (gleiche Höhe und Breite) bestehen!</i>
* <p/>
* Ansonsten würde ein ungewolltes "Verschieben" der verschiedenen Action-Figur-Verhalten
* passieren.
*
* @param spiegel
* Ob alle angelegten Figuren (der <i>Zustände</i> und <i>Aktionen</i>) an der X-Achse
* gespiegelt werden sollen.
*
* @see Figur#spiegelXSetzen(boolean)
*/
@API
@SuppressWarnings ( "unused" )
public void spiegelXSetzen (boolean spiegel) {
for (int i = 0; i < actions.length; i++) {
actions[i].spiegelXSetzen(spiegel);
}
for (int i = 0; i < states.length; i++) {
states[i].spiegelXSetzen(spiegel);
}
}
/**
* Spiegelt <b><u>alle</u> Figuren der <i>Zustände</i> und <i>Aktionen</i></b> dieser Figur an
* der Y-Achse.
* <p/>
* <b>ACHTUNG!</b>
* <p/>
* Damit diese Methode <b>nicht zu ungewollten Missverständnissen</b> führt, sollte folgendes
* beachtet werden:<br /> <i>Sämtliche einzelnen Figuren, die an dieser Action-Figur angemeldet
* sind, sollten <b>dieselben Maße haben</b>. Sie sollten also alle aus derselben Anzahl an
* "Unterquadraten" (gleiche Höhe und Breite) bestehen!</i>
* <p/>
* Ansonsten würde ein ungewolltes "Verschieben" der verschiedenen Action-Figur-Verhalten
* passieren.
*
* @param spiegel
* Ob alle angelegten Figuren (der <i>Zustände</i> und <i>Aktionen</i>) an der Y-Achse
* gespiegelt werden sollen.
*
* @see Figur#spiegelYSetzen(boolean)
*/
@API
@SuppressWarnings ( "unused" )
public void spiegelYSetzen (boolean spiegel) {
for (int i = 0; i < actions.length; i++) {
actions[i].spiegelYSetzen(spiegel);
}
for (int i = 0; i < states.length; i++) {
states[i].spiegelYSetzen(spiegel);
}
}
/**
* Färbt <b>alle</b> Figuren dieser Action-Figur in eine Farbe ein.
*
* @param farbe
* Die Farbe, die alle Felder aller Figuren annehmen werden. Als Standardfarben-String.
*
* @see Figur#einfaerben(Farbe)
* @see #einfaerben(Farbe)
*/
@API
@SuppressWarnings ( "unused" )
public void einfaerben (String farbe) {
einfaerben(Farbe.vonString(farbe));
}
/**
* Färbt <b>alle</b> Figuren dieser Action-Figur in eine Farbe ein.
*
* @param farbe
* Die Farbe, die alle Felder aller Figuren annehmen werden.
*
* @see Figur#einfaerben(Farbe)
* @see #einfaerben(String)
*/
@API
@SuppressWarnings ( "unused" )
public void einfaerben (Farbe farbe) {
for (int i = 0; i < actions.length; i++) {
actions[i].einfaerben(farbe);
}
for (int i = 0; i < states.length; i++) {
states[i].einfaerben(farbe);
}
}
/**
* Setzt den Größenfaktor <b>aller</b> anliegender Einzelfiguren dieser Action-Figur neu. Sowohl
* die einzelnen <i>Zustände</i> als auch die einzelnen <i>Aktionen</i>.
*
* @param faktor
* Der neue Größenfaktor
*
* @see Figur#faktorSetzen(int)
*/
@API
@SuppressWarnings ( "unused" )
public void faktorSetzen (int faktor) {
for (int i = 0; i < actions.length; i++) {
actions[i].faktorSetzen(faktor);
}
for (int i = 0; i < states.length; i++) {
states[i].faktorSetzen(faktor);
}
}
/**
* Vollführt einen Animationsschritt, sofern dies Sinn macht.
*
* @param runde
* Die aktuelle Runde
*/
private void animationsSchritt (int runde) {
if (performsAction) {
animationsActionSchritt(runde);
} else {
states[indexState].animationsSchritt(runde);
}
BoundingRechteck r = this.dimension();
for (int i = 0; i < actions.length; i++) {
actions[i].positionSetzen(r.x, r.y);
}
for (int i = 0; i < states.length; i++) {
states[i].positionSetzen(r.x, r.y);
}
}
/**
* Vollführt einen Animationsschritt für die aktuelle Aktion.
*
* @param runde
* Die aktuelle Runde
*/
private void animationsActionSchritt (int runde) {
int curr = actions[indexAction].aktuellesBild();
int last = actions[indexAction].animation().length - 1;
if (curr == last) {
if (runde % actions[indexAction].intervall() == 0) {
hatAktionSetzen(false);
}
} else {
actions[indexAction].animationsSchritt(runde);
}
}
/**
* Gibt zurück, ob diese Action-Figur gerade eine Aktion ausführt.
* <p/>
* Eine Aktion ausführen ist ein sehr kurzlebiger Zustand, daher ist dies nie dauerhaft gegeben,
* es sei denn, der Befehl hierzu wird dauerhaft aufgerufen.
*
* @return <code>true</code>, wenn diese Action-Figur gerade eine Aktion ausführt, sonst
* <code>false</code>.
*/
@API
@SuppressWarnings ( "unused" )
public boolean vollfuehrtAktion () {
return performsAction;
}
/**
* Zeichnet das Objekt.
*
* @param g
* Das zeichnende Graphics-Objekt
* @param r
* Das BoundingRechteck, das die Kameraperspektive repräsentiert.
* <p/>
* Hierbei soll zunächst getestet werden, ob das Objekt innerhalb der Kamera liegt, und erst
* dann gezeichnet werden.
*/
@Override
public void zeichnen (Graphics2D g, BoundingRechteck r) {
super.beforeRender(g, r);
if (performsAction) {
actions[indexAction].zeichnen(g, r);
} else {
states[indexState].zeichnen(g, r);
}
super.afterRender(g, r);
}
/**
* Berechnet ein minimales BoundingRechteck, das das Objekt <b>voll einschließt</b>.
*
* @return Ein BoundingRechteck mit dem minimal nötigen Umfang, um das Objekt <b>voll
* einzuschließen</b>.
*
* @see Raum#dimension()
*/
@API
@Override
public BoundingRechteck dimension () {
return performsAction ? actions[indexAction].dimension() : states[indexState].dimension();
}
/**
* Verschiebt die Actionfigur.
*
* @param v
* Die Verschiebung als Objekt der Klasse <code>Vektor</code>
*/
@API
@Override
@SuppressWarnings ( "unused" )
public void verschieben (Vektor v) {
for (int i = 0; i < states.length; i++) {
states[i].verschieben(v);
}
for (int i = 0; i < actions.length; i++) {
actions[i].verschieben(v);
}
}
/**
* {@inheritDoc}
*/
@Override
public Collider erzeugeCollider () {
return states[0].erzeugeCollider();
}
/**
* Berechnet exakt die derzeitig von dieser Figur okkupierten Flächen auf der Zeichenebene.
*
* @return Ein Array aus allen Flächen, die von dieser Figur <b>exakt</b> ausgefüllt werden.
*/
@Override
public BoundingRechteck[] flaechen () {
if (performsAction) {
return actions[indexAction].flaechen();
}
return states[indexState].flaechen();
}
}