All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.rpgframework.genericrpg.items.GearTool Maven / Gradle / Ivy

The newest version!
package de.rpgframework.genericrpg.items;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import de.rpgframework.core.RoleplayingSystem;
import de.rpgframework.genericrpg.ToDoElement;
import de.rpgframework.genericrpg.ToDoElement.Severity;
import de.rpgframework.genericrpg.chargen.OperationResult;
import de.rpgframework.genericrpg.data.ApplyTo;
import de.rpgframework.genericrpg.data.Choice;
import de.rpgframework.genericrpg.data.DataErrorException;
import de.rpgframework.genericrpg.data.Decision;
import de.rpgframework.genericrpg.data.IReferenceResolver;
import de.rpgframework.genericrpg.data.Lifeform;
import de.rpgframework.genericrpg.data.ReferenceError;
import de.rpgframework.genericrpg.data.ReferenceException;
import de.rpgframework.genericrpg.items.formula.ResolveFormulasInRequirementsStep;
import de.rpgframework.genericrpg.items.formula.ResolveFormulasStep;
import de.rpgframework.genericrpg.modification.Modification;
import de.rpgframework.genericrpg.modification.ModifiedObjectType;

/**
 * @author prelle
 *
 */
public class GearTool {

	public final static String SELECTION_NOT_ALLOWED = "Selection not allowed";

	private final static Logger logger = CarriedItem.logger;


	static CarriedItemProcessor[] PHASE1_STEPS = new CarriedItemProcessor[] {
			new ResolveVariantStep(),
			new CopyResolvedAttributesStep(),
			new ApplyDecisionsStep(),
			new ResolveFormulasStep(),
			new CopyRequirementsStep(),
			new ResolveFormulasInRequirementsStep(),
			new CopyUsagesStep()
			//new RecurseEmbeddedItems(),
	};

	static CarriedItemProcessor[] PHASE2_STEPS = new CarriedItemProcessor[] {
			new RecurseEmbeddedItems(),
	};

	private static Map PER_RPG_PHASE1_STEPS = new HashMap();
	private static Map PER_RPG_PHASE2_STEPS = new HashMap();

	//-------------------------------------------------------------------
	public static void setPerRPGStatsPhase1(RoleplayingSystem rules, CarriedItemProcessor[] steps) {
		PER_RPG_PHASE1_STEPS.put(rules, steps);
	}

	//-------------------------------------------------------------------
	public static void setPerRPGStatsPhase2(RoleplayingSystem rules, CarriedItemProcessor[] steps) {
		PER_RPG_PHASE2_STEPS.put(rules, steps);
	}

	//-------------------------------------------------------------------
	public static OperationResult validate(CarriedItem model) {
		return new OperationResult(true);
	}

	//-------------------------------------------------------------------
	public static  OperationResult> buildItem(T template, CarryMode mode, Lifeform charac, boolean strict, Decision...decisions) {
		return buildItem(template, mode, null, charac, strict, decisions);
	}

	//-------------------------------------------------------------------
	/**
	 * @param strict Don't ignore missing choices
	 */
	public static  OperationResult> buildItem(T template, CarryMode mode, PieceOfGearVariant variant, Lifeform charac, boolean strict, Decision...decisions) {
		return buildItem(template, mode, variant, charac, strict, null, decisions);
	}

	//-------------------------------------------------------------------
	/**
	 * @param strict Don't ignore missing choices
	 */
	public static  OperationResult> buildItem(T template, CarryMode mode, PieceOfGearVariant variant, Lifeform charac, boolean strict, IReferenceResolver context, Decision...decisions) {
		// Create an instance to store results
		CarriedItem ret = new CarriedItem(template, variant, mode);
		ret.setUser(charac);
		OperationResult> result = new OperationResult>(ret);
		List validDecisions = new ArrayList<>();

		List allChoices = new ArrayList<>(template.getChoices());
		if (variant!=null) {
			allChoices.addAll(variant.getChoices());
		}

		/*
		 * Add all decisions to CarriedItem. Validate each decision
		 */
		List expected = allChoices.stream().map(c -> c.getUUID()).collect(Collectors.toList());
		logger.log(Level.DEBUG, "Go over all {0} decisions", decisions.length);
		decisions:
		for (Decision dec : decisions) {
			if (dec.getChoiceUUID()==null) {
				result.addMessage(new ToDoElement(Severity.STOPPER, "Internal error: missing UUID in decision "+dec.getValue()));
				continue;
			}
			if (dec.getChoiceUUID()==PieceOfGear.VARIANT)
				continue;
			// Check if the referenced choice does exist
			Choice choice = template.getChoice(dec.getChoiceUUID());
			// If choice hasn't been found in the main object, try eventually selected variant
			if (choice==null && variant!=null) {
				choice = variant.getChoice(dec.getChoiceUUID());
			}
			if (choice==null) {
				result.addMessage(new ToDoElement(Severity.STOPPER, "Internal error: choice "+dec.getChoiceUUID()+" does not exist in "+template.getId()));
				continue;
			}
			expected.remove(dec.getChoiceUUID());
			// Check the value of the decision
			String[] options = choice.getChoiceOptions();
//			if (logger.isLoggable(Level.WARNING))
//				logger.log(Level.WARNING, "ToDo: validate ''{0}'' of type {1} with options {2}", dec.getValue(), choice.getChooseFrom(), Arrays.toString(options));
			if (choice.getChoiceOptions()!=null) {
			  boolean found = false;
				for (String opt : choice.getChoiceOptions()) {
					if (opt.equals(dec.getValue())) {
						// Decision matches element from option list
						if (choice.isNegated()) {
							// But those elements were marked as not allowed
							result.addMessage(new ToDoElement(Severity.STOPPER, SELECTION_NOT_ALLOWED+": "+choice.getTypeReference()+"="+dec.getValue()));
							continue decisions;
						} else {
							found = true;
							validDecisions.add(dec);
						}
						break;
					}
				}
				if (!found) {
					logger.log(Level.WARNING, template.getId()+": Invalid decision "+dec.getValue()+" for "+choice.getTypeReference()+"/"+choice.getUUID());
				}
			} else {
				// No choice options
				ModifiedObjectType type = choice.getChooseFrom();
				Object resolved = null;
				try {
					try {
						resolved = Integer.parseInt(dec.getValue());
					} catch (NumberFormatException e) {
						resolved = type.resolve(dec.getValue());
					}
				} catch (ReferenceException e) {
					logger.log(Level.ERROR, "In decision {0} of item {1}: {2}", dec, template, e.getMessage());
					result.addMessage(new ToDoElement(Severity.WARNING, "Could not resolve "+dec.getValue()));
					continue;
				}
				if (resolved==null) {
					System.err.println("GearTool: Could not resolve "+dec.getValue()+"   context="+context);
				}
				if (resolved==null && context!=null) {
					resolved = context.resolveItem(dec.getValue());
				}
				if (resolved!=null) {
					validDecisions.add(dec);
					if (logger.isLoggable(Level.INFO)) {
						logger.log(Level.INFO, template.getId()+": Valid decision "+dec.getValue()+" for "+type+"/"+choice.getUUID());
					}
				} else {
					logger.log(Level.ERROR, "Valid decision {0} points to unknown object: {1}", choice.getUUID(), dec.getValue());
					if (!strict) {
						validDecisions.add(dec);
					}
				}
			}
		}

		// For choices where decisions are missing, add the first decision
		for (UUID uuid : expected) {
			Choice choice = template.getChoice(uuid);
			if (choice==null && variant!=null) {
				choice = variant.getChoice(uuid);
			}
			if (choice==null) {
				logger.log(Level.WARNING, "Expected choice "+uuid+" but template of "+template.getId()+" does not know about it");
				throw new IllegalStateException("Expected choice "+uuid+" but template of "+template.getId()+" does not know about it");
			}
			if (choice.getChoiceOptions()!=null && choice.getChoiceOptions().length>0 && !strict) {
				logger.log(Level.DEBUG, "Add first option {0} for choice {1}:{2}", choice.getChoiceOptions()[0], choice.getChooseFrom(), choice.getTypeReference());
				validDecisions.add(new Decision(choice, choice.getChoiceOptions()[0]));
			}
		}

		logger.log(Level.DEBUG, template.getId()+": Set decisions = "+validDecisions);
		ret.setDecisions(validDecisions);

		OperationResult> res = recalculate("", template.getReferenceType(), charac, ret, strict);
		if (res.hasError()) {
			res.getMessages().forEach(m -> result.addMessage(m));
		}
		if (res.get()!=null && !res.get().isEmpty()) {
			logger.log(Level.DEBUG, "To Do: Add calculated character modifications to item {0}", ret);
			logger.log(Level.DEBUG, "-2--{0}", ret.getIncomingModifications());
			logger.log(Level.DEBUG, "-3--{0}", ret.getOutgoingModifications());
			for (Modification mod : res.get()) {
				logger.log(Level.DEBUG, "Add {0}    {1}", mod, mod.getApplyTo());
				if (mod.getApplyTo()==ApplyTo.DATA_ITEM || mod.getApplyTo()==null) {
					ret.addIncomingModification(mod);
				}

			}
		}
		return result;
	}

	//-------------------------------------------------------------------
	static CarriedItemProcessor[] concat(CarriedItemProcessor[] array1, CarriedItemProcessor[] array2) {
		CarriedItemProcessor[] result = Arrays.copyOf(array1, array1.length + array2.length);
	    System.arraycopy(array2, 0, result, array1.length, array2.length);
	    return result;
	}

	//--------------------------------------------------------------------
	protected static CarriedItemProcessor[] getSteps(CarriedItem carried) {
		RoleplayingSystem rules = null;
		CarriedItemProcessor[] extra1 = null;
		CarriedItemProcessor[] extra2 = null;
		try {
			if (!carried.getModifyable().getAssignedDataSets().isEmpty())
				rules = carried.getModifyable().getAssignedDataSets().iterator().next().getRules();
		} catch (Exception e) {
			logger.log(Level.WARNING, "Error processing CarriedItem "+carried.getUuid()+": "+e);
			throw e;
		}
		if (rules!=null) {
			extra1= PER_RPG_PHASE1_STEPS.get(rules);
			extra2= PER_RPG_PHASE2_STEPS.get(rules);
		}

		CarriedItemProcessor[] p1 = (extra1!=null)? concat(PHASE1_STEPS, extra1): PHASE1_STEPS;
		CarriedItemProcessor[] p2 = (extra2!=null)? concat(PHASE2_STEPS, extra2): PHASE2_STEPS;

//		logger.log(Level.WARNING, "Phase 1: ");
//		for (CarriedItemProcessor p : p1) logger.log(Level.WARNING, "  "+p.getClass());
//		logger.log(Level.WARNING, "Phase 2: ");
//		for (CarriedItemProcessor p : p2) logger.log(Level.WARNING, "  "+p.getClass());
		return concat(p1, p2);
	}

	public static  OperationResult> recalculate(String indent, ModifiedObjectType refType, Lifeform user, CarriedItem item) {
		return recalculate(indent, refType, user, item, true);
	}

	//--------------------------------------------------------------------
	/**
	 * - Copy resolved stats to attributes
	 * - Process decisions
	 * - Process modifications from "Modifications" (Permanent changes made to the base item)
	 * - Copy unresolved stats to attributes
	 *
	 * - Process modifications from accessories
	 */
	public static  OperationResult> recalculate(String indent, ModifiedObjectType refType, Lifeform user, CarriedItem item, boolean strict) {
//		logger.log(Level.ERROR, indent+"recalculate {0} of {1}",item.getKey(), (user!=null)?user.getName():null);
		if (item.getResolved()==null)
			throw new DataErrorException(null, new ReferenceError(null, item.getTemplateID()));
		String prefix = indent+item.getTemplateID()+": ";
		item.reset();
		logger.log(Level.DEBUG, prefix+"START:----------------"+item);

		// Recalculate accessories
		for (CarriedItem acc : item.getAccessories()) {
			recalculate(indent+"", refType, user, acc);
		}

		OperationResult> unprocessed = new OperationResult(new ArrayList<>());
		try {
			for (CarriedItemProcessor step : getSteps(item)) {
				if (logger.isLoggable(Level.TRACE))
					logger.log(Level.TRACE, prefix+"  run "+step.getClass().getSimpleName());
				OperationResult>  result = step.process(strict, refType, user, item, unprocessed.get());
				if (result.hasError()) {
					if (strict) {
						logger.log(Level.WARNING, "Error recalculating item {0}: {1}",item.getUuid(), result.getMessages());
//						return unprocessed;
					}
				} else {
					// Replace previous data with current
					unprocessed.set( result.get() );
				}
				// Copy all warnings to summarized object
//				if (!result.getMessages().isEmpty()) {
//					logger.log(Level.WARNING, "Step "+step+" added "+result.getMessages());
//				}
				unprocessed.getMessages().addAll(result.getMessages());
				if (logger.isLoggable(Level.TRACE))
					logger.log(Level.TRACE, prefix+"  after "+step.getClass().getSimpleName()+" = "+unprocessed+" / "+item.getOutgoingModifications());
//				logger.log(Level.TRACE, prefix+"  after "+step.getClass().getSimpleName()+"\n"+item.dump());
			}
			logger.log(Level.DEBUG, prefix+"  unprocessed = "+unprocessed);
			logger.log(Level.DEBUG, prefix+"  character   = "+item.getOutgoingModifications());
			logger.log(Level.DEBUG, prefix+"  messages    = "+unprocessed.getMessages());
			unprocessed.get().forEach( tmp -> item.addIncomingModification(tmp));
//		logger.log(Level.DEBUG, prefix+item.dump());
		} catch (Exception e) {
			logger.log(Level.ERROR, "Error recalculating item "+item.getKey()+" in "+user);
			e.printStackTrace();
		}
		logger.log(Level.DEBUG, prefix+"STOP :----------------"+item);

		item.setLastRecalculateResult(unprocessed);
		return unprocessed;
	}

}