/*******************************************************************************
* Copyright 2015 Maximilian Stark | Dakror <mail@dakror.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.dakror.vloxlands.game.entity.creature;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.ai.fsm.State;
import com.badlogic.gdx.ai.fsm.StateMachine;
import com.badlogic.gdx.ai.msg.MessageDispatcher;
import com.badlogic.gdx.ai.msg.Telegram;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Cell;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton.ImageButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.List;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.Array;
import de.dakror.vloxlands.Vloxlands;
import de.dakror.vloxlands.ai.MessageType;
import de.dakror.vloxlands.ai.SyncedStateMachine;
import de.dakror.vloxlands.ai.job.IdleJob;
import de.dakror.vloxlands.ai.job.Job;
import de.dakror.vloxlands.ai.job.WalkJob;
import de.dakror.vloxlands.ai.path.Path;
import de.dakror.vloxlands.ai.state.HelperState;
import de.dakror.vloxlands.game.Game;
import de.dakror.vloxlands.game.entity.structure.NodeType;
import de.dakror.vloxlands.game.entity.structure.Structure;
import de.dakror.vloxlands.game.item.Item;
import de.dakror.vloxlands.game.item.ItemStack;
import de.dakror.vloxlands.game.item.tool.Tool;
import de.dakror.vloxlands.game.voxel.Voxel;
import de.dakror.vloxlands.game.world.Island;
import de.dakror.vloxlands.ui.ItemSlot;
import de.dakror.vloxlands.ui.PinnableWindow;
import de.dakror.vloxlands.ui.TooltipImageButton;
import de.dakror.vloxlands.util.CurserCommand;
import de.dakror.vloxlands.util.event.VoxelSelection;
/**
* @author Dakror
*/
public class Human extends Creature {
public static final Vector3 resourceTrn = new Vector3(0.1f, 0.2f, -0.1f);
ItemStack carryingItemStack;
ModelInstance carryingItemModelInstance;
ItemStack tool;
ModelInstance toolModelInstance;
Array<Job> jobQueue = new Array<Job>();
Structure workPlace;
Structure location;
boolean createModelInstanceForCarryiedItemStack;
final Matrix4 tmp = new Matrix4();
StateMachine<Human> stateMachine;
Array<Object> previousStateParams = new Array<Object>();
public Array<Object> stateParams = new Array<Object>();
int tick;
boolean queueRotateTowardsTarget;
Path queueRotateTowardsTargetPath;
public Human(float x, float y, float z) {
super(x, y, z, "creature/humanblend/humanblend.g3db");
name = "Helper";
speed = 0.025f;
climbHeight = 1;
tool = new ItemStack();
carryingItemStack = new ItemStack();
stateMachine = new SyncedStateMachine<Human>(this);
stateMachine.setInitialState(HelperState.IDLE);
MessageDispatcher.getInstance().addListener(this, MessageType.STRUCTURE_BROADCAST.ordinal());
}
public void setTool(Item tool) {
if (tool == null) {
toolModelInstance = null;
this.tool.set(new ItemStack());
} else {
this.tool.setItem(tool);
this.tool.setAmount(1);
toolModelInstance = new ModelInstance(Vloxlands.assets.get("models/item/" + tool.getModel(), Model.class), new Matrix4());
}
}
public ItemStack getTool() {
return tool;
}
public ItemStack getCarryingItemStack() {
return carryingItemStack;
}
public void setCarryingItemStack(ItemStack carryingItemStack) {
this.carryingItemStack.set(carryingItemStack);
if (carryingItemStack.isNull()) carryingItemModelInstance = null;
else createModelInstanceForCarryiedItemStack = true;
}
@Override
public void tick(int tick) {
super.tick(tick);
this.tick = tick;
Job j = firstJob();
if (j != null && j.isActive() && !j.isDone()) j.tick(tick);
}
@Override
public void update(float delta) {
super.update(delta);
stateMachine.update();
if (queueRotateTowardsTarget) {
rotateTowardsGhostTarget(queueRotateTowardsTargetPath);
queueRotateTowardsTarget = false;
queueRotateTowardsTargetPath = null;
}
Job j = firstJob();
if (j != null) {
if (j.isActive()) {
if (j.isDone()) {
jobQueue.removeIndex(0);
j.onEnd();
j.triggerEndEvent();
if (j.isPersistent()) {
j.resetState();
queueJob(null, j);
}
}
} else {
j.trigger(tick);
if (j instanceof WalkJob && path != ((WalkJob) j).getPath() && !j.isDone()) path = ((WalkJob) j).getPath();
}
}
}
@Override
public void renderAdditional(ModelBatch batch, Environment environment) {
if (!carryingItemStack.isNull() && carryingItemModelInstance != null) {
tmp.setToRotation(Vector3.Y, 0).translate(posCache);
tmp.rotate(Vector3.Y, rotCache.getYaw());
tmp.translate(resourceTrn);
carryingItemModelInstance.transform.set(tmp);
}
if (!tool.isNull() && toolModelInstance.transform != null) {
tmp.setToRotation(Vector3.Y, 0).translate(posCache);
tmp.rotate(Vector3.Y, rotCache.getYaw());
((Tool) tool.getItem()).transformInHand(tmp, this);
toolModelInstance.transform.set(tmp);
}
if (createModelInstanceForCarryiedItemStack) {
Vector3 tr = new Vector3(-0.2f, 0, -0.3f);
Model model = null;
if (carryingItemStack.getItem().isModel()) {
model = Vloxlands.assets.get("models/item/" + carryingItemStack.getItem().getModel(), Model.class);
tr.add(0.1f, 0.05f, 0.1f);
} else if (carryingItemStack.getItem().getModel().startsWith("voxel:")) {
Voxel v = Voxel.getForId(Integer.parseInt(carryingItemStack.getItem().getModel().replace("voxel:", "").trim()));
ModelBuilder mb = new ModelBuilder();
mb.begin();
mb.part("voxel", v.getMesh(), GL20.GL_TRIANGLES, Game.world.getOpaque());
model = mb.end();
} else {
Gdx.app.error("Human.setCarryingItemStack", "Can't handle item model!");
return;
}
carryingItemModelInstance = new ModelInstance(model, new Matrix4());
carryingItemModelInstance.nodes.get(0).scale.set(0.2f, 0.2f, 0.2f);
carryingItemModelInstance.nodes.get(0).translation.set(tr);
carryingItemModelInstance.calculateTransforms();
createModelInstanceForCarryiedItemStack = false;
}
if (((jobQueue.size > 0 && firstJob().isUsingTool()) || (jobQueue.size > 1 && jobQueue.get(1).isUsingTool())) && toolModelInstance != null) batch.render(toolModelInstance, environment);
else if (!carryingItemStack.isNull() && carryingItemModelInstance != null) batch.render(carryingItemModelInstance, environment);
}
public void queueJob(Path path, Job job) {
if (job == null) {
if (path.size() > 0) {
WalkJob wj = new WalkJob(path, this);
jobQueue.add(wj);
wj.queue();
}
} else {
if (path != null && path.size() > 0) {
WalkJob wj = new WalkJob(path, this);
jobQueue.add(wj);
wj.queue();
} else {
queueRotateTowardsTarget = true;
queueRotateTowardsTargetPath = path;
}
jobQueue.add(job);
job.queue();
}
}
public void setJob(Path path, Job job) {
jobQueue.clear();
queueJob(path, job);
}
public Job firstJob() {
if (jobQueue.size == 0) return null;
return jobQueue.first();
}
@Override
public void onVoxelSelection(VoxelSelection vs, boolean lmb) {
if ((wasSelected || selected) && !lmb && location == null && workPlace == null) {
selected = true;
changeState(HelperState.WALK_TO_TARGET, vs.voxelPos.getPos());
}
}
@Override
public void onStructureSelection(Structure structure, boolean lmb) {
if ((wasSelected || selected) && !lmb && location == null && workPlace == null) {
selected = true;
CurserCommand c = structure.getCommandForEntity(this);
if (c == CurserCommand.WALK) {
changeState(HelperState.WALK_TO_TARGET, structure.getStructureNode(posCache, NodeType.target).pos.cpy().add(structure.getVoxelPos()));
}
}
}
@Override
public void onVoxelRangeSelection(Island island, Vector3 start, Vector3 end, boolean lmb) {}
@Override
public void onReachTarget() {
super.onReachTarget();
if (firstJob() instanceof WalkJob) firstJob().setDone();
}
public Array<Job> getJobQueue() {
return jobQueue;
}
public boolean isIdle() {
return jobQueue.size == 0;
}
public Structure getWorkPlace() {
return workPlace;
}
public void setWorkPlace(Structure workPlace) {
this.workPlace = workPlace;
}
public Structure getLocation() {
return location;
}
public void setLocation(Structure location) {
this.location = location;
visible = location == null;
}
@Override
public boolean handleMessage(Telegram msg) {
return stateMachine.handleMessage(msg);
}
public void changeState(State<Human> newState, Object... params) {
previousStateParams.clear();
previousStateParams.addAll(stateParams);
stateParams.clear();
stateParams.addAll(params);
stateMachine.changeState(newState);
}
public void revertToPreviousState() {
stateParams.clear();
stateParams.addAll(previousStateParams);
stateMachine.revertToPreviousState();
}
public State<Human> getState() {
return stateMachine.getCurrentState();
}
public StateMachine<Human> getStateMachine() {
return stateMachine;
}
@Override
public void dispose() {
super.dispose();
MessageDispatcher.getInstance().removeListener(this, MessageType.STRUCTURE_BROADCAST.ordinal());
}
@Override
public void setUI(final PinnableWindow window, Object... params) {
window.row().pad(0).colspan(50).padRight(-10).fillX();
final List<Job> jobs = new List<Job>(Vloxlands.skin);
jobs.setItems(new IdleJob(this));
jobs.addAction(new Action() {
@Override
public boolean act(float delta) {
if (jobQueue.size == 0 && jobs.getItems().get(0) instanceof IdleJob) return false;
if (!jobQueue.equals(jobs.getItems())) {
if (jobQueue.size > 0) jobs.setItems(jobQueue);
else jobs.setItems(new IdleJob(Human.this));
jobs.getSelection().setDisabled(true);
jobs.setSelectedIndex(-1);
window.pack();
}
return false;
}
});
jobs.getSelection().setDisabled(true);
jobs.setSelectedIndex(-1);
jobs.getStyle().selection.setLeftWidth(10);
jobs.getStyle().selection.setTopHeight(3);
final ScrollPane jobsWrap = new ScrollPane(jobs, Vloxlands.skin);
jobsWrap.setVisible(false);
jobsWrap.setScrollbarsOnTop(false);
jobsWrap.setFadeScrollBars(false);
final Cell<?> cell = window.add(jobsWrap).height(0);
window.row();
ItemSlot tool = new ItemSlot(window.getStage(), this.tool);
window.left().add(tool).spaceRight(2);
ItemSlot slot = new ItemSlot(window.getStage(), carryingItemStack);
window.add(slot).spaceRight(2);
ItemSlot armor = new ItemSlot(window.getStage(), new ItemStack());
window.add(armor).spaceRight(2);
ImageButtonStyle style = new ImageButtonStyle(Vloxlands.skin.get("image_toggle", ButtonStyle.class));
style.imageUp = Vloxlands.skin.getDrawable("queue");
style.imageUp.setMinWidth(ItemSlot.size);
style.imageUp.setMinHeight(ItemSlot.size);
style.imageDown = Vloxlands.skin.getDrawable("queue");
style.imageDown.setMinWidth(ItemSlot.size);
style.imageDown.setMinHeight(ItemSlot.size);
final TooltipImageButton job = new TooltipImageButton(style);
window.getStage().addActor(job.getTooltip());
job.setName("job");
ClickListener cl = new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
cell.height(cell.getMinHeight() == 100 ? 0 : 100);
jobsWrap.setVisible(!jobsWrap.isVisible());
window.invalidateHierarchy();
window.pack();
}
};
job.addListener(cl);
job.getTooltip().set("Job Queue", "Toggle Job Queue display");
window.add(job).padRight(-10);
if (params[0] == Boolean.TRUE) {
job.setChecked(true);
cl.clicked(null, 0, 0);
}
}
}