/*
* Aphelion
* Copyright (c) 2013 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 (for example by appending a copyright notice).
*
* 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.shared.gameconfig;
import aphelion.shared.swissarmyknife.SwissArmyKnife;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*
* @param <GCSIMPLE>
* @param <TYPE>
* @author Joris
*/
public abstract class GCSimpleList<GCSIMPLE extends SimpleAbstract<TYPE>, TYPE> extends WrappedValueAbstract
{
protected final WrappedValueAbstract.Factory valueFactory;
protected SimpleAbstract<TYPE>[] value;
private final int key_hashCode;
private Set<TYPE> valueSet;
private LIST_FUNCTION listFunction = LIST_FUNCTION.REPEAT;
private boolean needsSeed;
public static enum LIST_FUNCTION
{
REPEAT ("~REPEAT"),
REPEAT_ALL ("~REPEAT_ALL"),
LINEAR ("~LINEAR"),
SHUFFLE ("~SHUFFLE"),
RAND ("~RAND");
/** name in config */
public final String name;
private final static LIST_FUNCTION[] values = values();
LIST_FUNCTION(String name)
{
this.name = name;
}
@Override
public String toString()
{
return name;
}
public static LIST_FUNCTION byName(String name)
{
for (LIST_FUNCTION func : values)
{
if (func.name.equals(name))
{
return func;
}
}
return null;
}
};
// [REPEAT, 1, 2, 4] = [1, 2, 4, 4, 4, 4]
// [REPEAT_ALL, 1, 2, 4] = [1, 2, 4, 1, 2, 4]
// [LINEAR, 1, 2, 4] = [1, 2, 4, 6, 8, 10]
// [LINEAR, 1, 4, 2] = [1, 4, 2, 0, -2, -4]
// [LINEAR, 4] = [4, 8, 12, 16, 20, 24]
// [SHUFFLE, 1, 2, 3, 4, 5] = [5, 3, 3, 2, 2, 1, 4]
// [RAND, 5, 5, 6, 10] = [5, 6, 9, 10, 6, 6, 7] # (min,max,min,max)
// .get(int index)
// .get(int index, long seed=tick^actor.seed) // jenkinMix(seed, projectile_index, key.hashCode())
GCSimpleList(ConfigSelection selection, String key, WrappedValueAbstract.Factory factory)
{
super(selection, key);
key_hashCode = key == null ? 0 : key.hashCode();
this.valueFactory = factory;
this.value = new SimpleAbstract[] { (SimpleAbstract) valueFactory.create(null, null) };
this.value[0].newValue(null);
}
protected abstract TYPE calculateLinear(TYPE from, TYPE to, int factor);
protected abstract TYPE calculateRand(TYPE from, TYPE to, int rand);
public boolean needsSeed()
{
return needsSeed;
}
public TYPE getBoxed(int index)
{
return getBoxed(index, 0);
}
public TYPE getBoxed(int index, int seed)
{
assert value.length > 0;
switch (listFunction)
{
case REPEAT:
if (index < value.length)
{
return value[index].getBoxed();
}
else
{
return value[value.length-1].getBoxed();
}
case REPEAT_ALL:
return value[index % value.length].getBoxed();
case LINEAR:
if (index < value.length)
{
return value[index].getBoxed();
}
else
{
if (value.length > 1)
{
return this.calculateLinear(
value[value.length-2].getBoxed(),
value[value.length-1].getBoxed(),
index - value.length);
}
else
{
return this.calculateLinear(
null,
value[value.length-1].getBoxed(),
index - value.length + 1);
}
}
case SHUFFLE:
{
int rnd = SwissArmyKnife.jenkinMix(seed, index, key_hashCode);
int i = rnd % value.length;
return value[i].getBoxed();
}
case RAND:
{
int rnd = SwissArmyKnife.jenkinMix(seed, index, key_hashCode);
int from;
int to ;
if (value.length == 1)
{
from = 0;
to = 0;
}
else
{
from = index * 2;
to = from + 1;
if (to >= value.length)
{
// use the last 1 or 2 values
if ((value.length & 1) == 0) // even
{
from = value.length - 2;
to = from + 1;
}
else // odd
{
from = value.length - 1;
to = from;
}
}
}
return this.calculateRand(
from == to ? null : value[from].getBoxed(),
value[to].getBoxed(),
rnd);
}
}
throw new AssertionError();
}
public boolean hasIndex(int index)
{
if (!set)
{
return false;
}
return index < value.length;
}
public boolean isIndexSet(int index)
{
if (value == null)
{
return false;
}
if (index < value.length)
{
return value[index].isSet();
}
else
{
return value[value.length-1].isSet();
}
}
public int getValuesLength()
{
return value == null ? 0 : value.length;
}
public boolean hasValue(TYPE val)
{
if (this.valueSet == null)
{
this.valueSet = new HashSet<>(this.value.length);
for (SimpleAbstract<TYPE> wrapper : this.value)
{
this.valueSet.add(wrapper.getBoxed());
}
}
return valueSet.contains(val);
}
@Override
boolean newValue(Object value)
{
boolean dirty = false;
List list;
LIST_FUNCTION oldListFunc = this.listFunction;
if (value instanceof List)
{
list = (List) value;
}
else
{
list = new ArrayList(1);
list.add(value);
}
this.listFunction = LIST_FUNCTION.REPEAT; // default
boolean skipFirst = false;
if (list.get(0) instanceof String)
{
this.listFunction = LIST_FUNCTION.byName((String) list.get(0));
if (this.listFunction == null)
{
this.listFunction = LIST_FUNCTION.LINEAR;
}
else
{
skipFirst = true;
}
}
needsSeed = this.listFunction == LIST_FUNCTION.SHUFFLE || this.listFunction == LIST_FUNCTION.RAND;
int neededSize = list.size() - (skipFirst ? 1 : 0);
if (neededSize < 1) neededSize = 1;
if (this.value == null || neededSize != this.value.length)
{
this.value = (GCSIMPLE[]) new SimpleAbstract[neededSize];
this.value[0] = (GCSIMPLE) valueFactory.create(null, null);
}
for (int i = skipFirst ? 1 : 0, valueIndex = 0;
i < list.size();
++i, ++valueIndex)
{
if (this.value[valueIndex] == null)
{
this.value[valueIndex] = (GCSIMPLE) valueFactory.create(null, null);
}
if (this.value[valueIndex].newValue(list.get(i)))
{
dirty = true;
}
}
boolean wasSet = this.set;
set = isIndexSet(this.value.length - 1);
if (wasSet != set || oldListFunc != this.listFunction)
{
dirty = true;
}
if (dirty)
{
this.valueSet = null;
fireChangeListener();
}
return dirty;
}
}