/* * Copyright 2010, Maarten Billemont * * 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 com.lyndir.omicron.api; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.omicron.api.error.ExceptionUtils.assertState; import com.google.common.collect.*; import com.lyndir.lhunath.opal.system.util.*; import com.lyndir.omicron.api.error.NotAuthenticatedException; import com.lyndir.omicron.api.util.Maybe; import com.lyndir.omicron.api.util.PathUtils; import edu.umd.cs.findbugs.annotations.*; import java.lang.SuppressWarnings; import java.util.*; import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class ConstructorModule extends Module implements IConstructorModule, IConstructorModuleController { private final int buildSpeed; private final ModuleType<?> buildsModule; private boolean resourceConstrained; private int remainingSpeed; @Nullable private GameObject target; protected ConstructorModule(final ImmutableResourceCost resourceCost, final int buildSpeed, final ModuleType<?> buildsModule) { super( resourceCost ); this.buildSpeed = buildSpeed; this.buildsModule = buildsModule; } static Builder0 createWithStandardResourceCost() { return createWithExtraResourceCost( ImmutableResourceCost.immutable() ); } static Builder0 createWithExtraResourceCost(final ImmutableResourceCost resourceCost) { return new Builder0( ModuleType.CONSTRUCTOR.getStandardCost().add( resourceCost ) ); } @Override protected void onReset() { resourceConstrained = false; remainingSpeed = buildSpeed; } @Override protected void onNewTurn() { } // This method assumes a target link between this module and the site exists. private void construct(final ConstructionSite site) { if (isResourceConstrained() || remainingSpeed <= 0) return; ChangeInt.From remainingSpeedChange = ChangeInt.from( remainingSpeed ); construction: for (; remainingSpeed > 0; --remainingSpeed) { /* Find resource cost */ Optional<ImmutableResourceCost> resourceCostOptional = site.getResourceCostToPerformWork( getBuildsModule() ); if (!resourceCostOptional.isPresent()) // No work left to do. break; final MutableResourceCost resourceCost = ResourceCost.mutable( resourceCostOptional.get() ); /* Find resource stock to cover cost */ // Initialize path finding functions. PredicateNN<IGameObject> foundFunction = gameObject -> { for (final ContainerModule containerModule : gameObject.getModules( ModuleType.CONTAINER )) if (resourceCost.get( containerModule.getResourceType() ) > 0 && containerModule.getStock() > 0) return true; return false; }; NNFunctionNN<PathUtils.Step<IGameObject>, Double> costFunction = gameObjectStep -> 1d; NNFunctionNN<IGameObject, Stream<? extends IGameObject>> neighboursFunction = gameObject -> { Maybe<? extends ITile> location = gameObject.getLocation(); if (!location.isPresent()) return ImmutableList.<IGameObject>of().stream(); return location.get().neighbours().stream() // .map( new Function<ITile, IGameObject>() { @Override public IGameObject apply(final ITile gameObject_) { return gameObject_.getContents().orNull(); } } ) // .filter( gameObject_ -> gameObject_ != null ); }; /* Find paths to containers and deposit mined resources. */ ImmutableMap.Builder<ContainerModule, Integer> borrowedResources = ImmutableMap.builder(); while (!resourceCost.isZero()) { Optional<PathUtils.Path<IGameObject>> path = PathUtils.find( getGameObject(), foundFunction, costFunction, Constants.MAX_DISTANCE_TO_CONTAINER, neighboursFunction ); if (!path.isPresent()) { resourceConstrained = true; // No more containers with available stock: not enough resources available to complete work unit. // Give borrowed resources back to containers. for (final Map.Entry<ContainerModule, Integer> borrowEntry : borrowedResources.build().entrySet()) borrowEntry.getKey().addStock( borrowEntry.getValue() ); break construction; } for (final ContainerModule containerModule : path.get().getTarget().getModules( ModuleType.CONTAINER )) { int moduleResourceCost = resourceCost.get( containerModule.getResourceType() ); int depletedStock = containerModule.depleteStock( moduleResourceCost ); borrowedResources.put( containerModule, depletedStock ); resourceCost.reduce( containerModule.getResourceType(), depletedStock ); } } /* Complete a unit of work. */ if (!site.performWork( getBuildsModule() )) // Failed to perform unit of work. Shouldn't happen: this condition was tested at the beginning of the iteration. // Give borrowed resources back to containers. for (final Map.Entry<ContainerModule, Integer> borrowEntry : borrowedResources.build().entrySet()) borrowEntry.getKey().addStock( borrowEntry.getValue() ); } getGameObject().getGame().getController().fireIfObservable( getGameObject() ) // .onConstructorWorked( this, remainingSpeedChange.to( remainingSpeed ) ); } @Override public ModuleType<?> getBuildsModule() { return buildsModule; } @Override public int getBuildSpeed() { return buildSpeed; } @Override public boolean isResourceConstrained() { return resourceConstrained; } @Override public int getRemainingSpeed() { return remainingSpeed; } @Nullable @Override public GameObject getTarget() { return target; } @Override public void setTarget(@Nullable final IGameObject target) { Change.From<IGameObject> targetChange = Change.<IGameObject>from( this.target ); this.target = GameObject.castN( target ); Security.currentGame().getController().fireIfObservable( getGameObject() ) // .onConstructorTargeted( this, targetChange.to( this.target ) ); } @Override public ImmutableSet<? extends UnitType> blueprints() { return ImmutableSet.copyOf( UnitTypes.values() ); } /** * Schedule the construction of a new unit of the given type on the given location. * * @param unitType The type of unit to construct. * @param location The location to construct the new unit. It must be accessible and adjacent to this module's game object. * * @return The job that will be created for the construction of the new unit. */ @Override public ConstructionSite schedule(final IUnitType unitType, final ITile location) throws NotAuthenticatedException, InaccessibleException, IncompatibleLevelException, OutOfRangeException { Tile ownLocation = getGameObject().getLocation().get(); assertState( location.isAccessible().isTrue(), InaccessibleException.class ); assertState( location.getLevel().equals( ownLocation.getLevel() ), IncompatibleLevelException.class ); assertState( location.getPosition().distanceTo( ownLocation.getPosition() ) == 1, OutOfRangeException.class ); ConstructionSite site = new ConstructionSite( (UnitType) unitType, getGameObject().getGame(), getGameObject().getOwner().get(), Tile.cast( location ) ); site.register(); setTarget( site ); return site; } @Override public IConstructorModuleController getController() { return this; } @Override public IConstructorModule getModule() { return this; } /** * A construction site is a unit that is under construction. Its controller manages its construction progress and it turns into the * constructed unit upon completion. */ @SuppressFBWarnings({ "EQ_DOESNT_OVERRIDE_EQUALS" }) public static class ConstructionSite extends GameObject implements IConstructionSite { private final UnitType constructionUnitType; private final Map<PublicModuleType<?>, Integer> remainingWork = Collections.synchronizedMap( Maps.<PublicModuleType<?>, Integer>newHashMap() ); private final List<? extends Module> constructionModules; private ConstructionSite(@Nonnull final UnitType constructionUnitType, @Nonnull final Game game, @Nonnull final Player owner, final Tile location) { super( UnitTypes.CONSTRUCTION, game, owner, location ); this.constructionUnitType = constructionUnitType; constructionModules = constructionUnitType.createModules(); for (final Module module : constructionModules) remainingWork.put( module.getType(), ifNotNullElse( remainingWork.get( module.getType() ), 0 ) + constructionUnitType.getConstructionWork() ); } @Override public int getRemainingWork(final PublicModuleType<?> moduleType) { return ifNotNullElse( remainingWork.get( moduleType ), 0 ); } @Override public ImmutableResourceCost getRemainingResourceCost() { MutableResourceCost remainingResourceCost = ResourceCost.mutable(); for (final Module constructionModule : constructionModules) remainingResourceCost.add( constructionModule.getResourceCost().multiply( getRemainingWork( constructionModule.getType() ) ) ); return ResourceCost.immutable( remainingResourceCost ); } @Override public Optional<ImmutableResourceCost> getResourceCostToPerformWork(final PublicModuleType<?> moduleType) { for (final Module constructionModule : constructionModules) if (constructionModule.getType().equals( moduleType ) && getRemainingWork( constructionModule.getType() ) > 0) return Optional.of( constructionModule.getResourceCost() ); return Optional.empty(); } /** * Reduce the amount of work left for building the modules of the given type by one unit. * * @param moduleType The module for which a constructor wants to complete a unit of work for. * * @return {@code true} if there was work left to complete for the given module type and a unit of work has now been completed. */ private boolean performWork(final ModuleType<?> moduleType) { int remaining = getRemainingWork( moduleType ); if (remaining > 0) { ChangeInt.From remainingWorkChange = ChangeInt.from( remainingWork.put( moduleType, --remaining ) ); getGame().getController().fireIfObservable( this ) // .onConstructionSiteWorked( this, moduleType, remainingWorkChange.to( remaining ) ); return true; } return false; } @Nonnull @Override public GameObjectController<? extends GameObject> getController() { return new GameObjectController<GameObject>( this ) { @Override protected void onNewTurn() { super.onNewTurn(); // Initialize path finding functions. PredicateNN<IGameObject> foundFunction = gameObject -> { for (final ConstructorModule module : gameObject.getModules( ModuleType.CONSTRUCTOR )) if (module.getRemainingSpeed() > 0 && !module.isResourceConstrained() && getRemainingWork( module.getBuildsModule() ) > 0) return true; return false; }; NNFunctionNN<PathUtils.Step<IGameObject>, Double> costFunction = gameObjectStep -> 1d; NNFunctionNN<IGameObject, Stream<? extends IGameObject>> neighboursFunction = neighbourInput -> { ITile location = neighbourInput.getLocation().get(); return location.neighbours().stream().map( new Function<ITile, IGameObject>() { @Override public IGameObject apply(final ITile tile) { Maybe<? extends IGameObject> contents = tile.getContents(); if (contents.isPresent()) for (final ConstructorModule module : contents.get().getModules( ModuleType.CONSTRUCTOR )) if (neighbourInput.equals( module.getTarget() )) return contents.get(); return null; } } ).filter( gameObject -> gameObject != null ); }; // Find paths to constructor and use them to work on the job. while (true) { Optional<PathUtils.Path<IGameObject>> path = PathUtils.find( getGameObject(), foundFunction, costFunction, Constants.MAX_DISTANCE_TO_CONSTRUCTOR, neighboursFunction ); if (!path.isPresent()) // No more constructors with remaining speed or construction finished. break; for (final ConstructorModule constructorModule : path.get().getTarget().getModules( ModuleType.CONSTRUCTOR )) constructorModule.construct( ConstructionSite.this ); } // Check if we managed to complete all the work. synchronized (remainingWork) { if (FluentIterable.from( remainingWork.values() ).filter( remainingWork1 -> remainingWork1 > 0 ).isEmpty()) // No more work remaining; create the constructed unit. replaceWith( new GameObject( constructionUnitType, getGame(), getOwner().get(), getLocation().get() ) ); } } }; } } @SuppressWarnings({ "ParameterHidesMemberVariable", "InnerClassFieldHidesOuterClassField" }) static class Builder0 { private final ImmutableResourceCost resourceCost; private Builder0(final ImmutableResourceCost resourceCost) { this.resourceCost = resourceCost; } Builder1 buildSpeed(final int buildSpeed) { return new Builder1( buildSpeed ); } class Builder1 { private final int buildSpeed; private Builder1(final int buildSpeed) { this.buildSpeed = buildSpeed; } ConstructorModule buildsModule(final ModuleType<?> buildsModule) { return new ConstructorModule( resourceCost, buildSpeed, buildsModule ); } } } }