package com.planet_ink.coffee_mud.Abilities.Common; import com.planet_ink.coffee_mud.Abilities.StdAbility; import com.planet_ink.coffee_mud.core.interfaces.*; import com.planet_ink.coffee_mud.core.*; import com.planet_ink.coffee_mud.core.collections.*; import com.planet_ink.coffee_mud.Abilities.interfaces.*; import com.planet_ink.coffee_mud.Areas.interfaces.*; import com.planet_ink.coffee_mud.Behaviors.interfaces.*; import com.planet_ink.coffee_mud.CharClasses.interfaces.*; import com.planet_ink.coffee_mud.Commands.interfaces.*; import com.planet_ink.coffee_mud.Common.interfaces.*; import com.planet_ink.coffee_mud.Exits.interfaces.*; import com.planet_ink.coffee_mud.Items.interfaces.*; import com.planet_ink.coffee_mud.Libraries.interfaces.*; import com.planet_ink.coffee_mud.Locales.interfaces.*; import com.planet_ink.coffee_mud.MOBS.interfaces.*; import com.planet_ink.coffee_mud.Races.interfaces.*; import java.util.*; /* Copyright 2002-2016 Bo Zimmerman 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. */ public class CommonSkill extends StdAbility { @Override public String ID() { return "CommonSkill"; } private final static String localizedName = CMLib.lang().L("Common Skill"); @Override public String name() { return localizedName; } private static final String[] triggerStrings = empty; @Override public String[] triggerStrings() { return triggerStrings; } public String supportedResourceString() { return ""; } public static final Map<String, Integer[]> resourcesMap = new Hashtable<String, Integer[]>(); protected static Item fakeFire = null; protected static final List<String> uninvokeEmpties = new ReadOnlyList<String>(new ArrayList<String>(0)); protected volatile Room activityRoom = null; protected boolean aborted = false; protected boolean helping = false; protected boolean bundling = false; public Ability helpingAbility = null; protected volatile int tickUp = 0; protected String verb = L("working"); protected String playSound = null; protected int yield = baseYield(); protected volatile int lastBaseDuration= 0; protected int baseYield() { return 1; } @Override public int abstractQuality() { return Ability.QUALITY_INDIFFERENT; } protected String displayText = L("(Doing something productive)"); @Override public String displayText() { return displayText; } @Override protected ExpertiseLibrary.SkillCostDefinition getRawTrainingCost() { return CMProps.getCommonSkillGainCost(ID()); } @Override protected int iniPracticesToPractice() { return 1; } protected boolean allowedWhileMounted() { return true; } @Override public int usageType() { return USAGE_MOVEMENT; } protected boolean allowedInTheDark() { return false; } @Override protected int canAffectCode() { return Ability.CAN_MOBS; } @Override protected int canTargetCode() { return Ability.CAN_ITEMS; } protected List<String> getUninvokeException() { return uninvokeEmpties; } @Override public int classificationCode() { return Ability.ACODE_COMMON_SKILL; } protected boolean canBeDoneSittingDown() { return false; } protected int getActivityMessageType() { return canBeDoneSittingDown() ? CMMsg.MSG_HANDS | CMMsg.MASK_SOUND : CMMsg.MSG_NOISYMOVEMENT; } @Override public int abilityCode() { return yield; } @Override public void setAbilityCode(int newCode) { yield = newCode; } @Override public boolean okMessage(final Environmental myHost, final CMMsg msg) { if(!super.okMessage(myHost, msg)) return false; if((myHost instanceof MOB)&&(myHost == this.affected)&&(((MOB)myHost).location()!=null)) { if((msg.sourceMinor()==CMMsg.TYP_SHUTDOWN) ||((msg.sourceMinor()==CMMsg.TYP_QUIT)&&(msg.amISource((MOB)myHost)))) { aborted=true; unInvoke(); } } return true; } @Override public boolean tick(Tickable ticking, int tickID) { if((affected instanceof MOB)&&(tickID==Tickable.TICKID_MOB)) { final MOB mob=(MOB)affected; if((mob.isInCombat()) ||(mob.location()!=activityRoom) ||(!CMLib.flags().isAliveAwakeMobileUnbound(mob,true))) { aborted=true; unInvoke(); return false; } final String sound=(playSound!=null)?CMLib.protocol().msp(playSound,10):""; if(tickDown==4) mob.location().show(mob,null,getActivityMessageType(),L("<S-NAME> <S-IS-ARE> almost done @x1.@x2",verb,sound)); else if((tickUp%4)==0) { final int total=tickUp+tickDown; final int pct=(int)Math.round(CMath.div(tickUp,total)*100.0); mob.location().show(mob,null,this,getActivityMessageType(),L("<S-NAME> continue(s) @x1 (@x2% completed).@x3",verb,""+pct,sound),null,L("<S-NAME> continue(s) @x1.@x2",verb,sound)); } if((helping) &&(helpingAbility!=null) &&(helpingAbility.affecting() instanceof MOB) &&(((MOB)helpingAbility.affecting()).isMine(helpingAbility))) helpingAbility.tick(helpingAbility.affecting(),tickID); if((mob.soulMate()==null) &&(mob.playerStats()!=null) &&(mob.location()!=null) &&(!CMSecurity.isDisabled(CMSecurity.DisFlag.HYGIENE))) mob.playerStats().adjHygiene(PlayerStats.HYGIENE_COMMONDIRTY); } final int preTickDown=tickDown; if(!super.tick(ticking,tickID)) return false; tickUp+=(preTickDown-tickDown); return true; } @Override public void unInvoke() { if(canBeUninvoked() && (!super.unInvoked)) { if((affected!=null) &&(affected instanceof MOB) &&(((MOB)affected).location()!=null)) { final MOB mob=(MOB)affected; if(aborted) mob.location().show(mob,null,getActivityMessageType(),L("<S-NAME> stop(s) @x1.",verb)); else mob.location().show(mob,null,getActivityMessageType(),L("<S-NAME> <S-IS-ARE> done @x1.",verb)); helping=false; helpingAbility=null; } } super.unInvoke(); } protected int getDuration(int baseTicks, MOB mob, int itemLevel, int minDuration) { int ticks=baseTicks; final int level=mob.phyStats().level() - itemLevel; final double pct=CMath.div(level,CMProps.getIntVar(CMProps.Int.LASTPLAYERLEVEL))*.5; ticks-=(int)Math.round(CMath.mul(ticks, pct)); lastBaseDuration=ticks; if(lastBaseDuration<minDuration) lastBaseDuration=minDuration; final double quickPct = getXTIMELevel(mob) * 0.05; ticks-=(int)Math.round(CMath.mul(ticks, quickPct)); if(ticks<minDuration) ticks=minDuration; return ticks; } @Override protected int addedTickTime(MOB invokerMOB, int baseTickTime) { // common skills tend to SUBTRACT time -- not add to it! return 0; } protected String getBrand(MOB mob) { if(mob==null) return "This is the work of an anonymous craftsman."; else return "This is the work of "+mob.Name()+"."; } protected void commonTell(MOB mob, Environmental target, Environmental tool, String str) { if(mob.isMonster()&&(mob.amFollowing()!=null)) { if(str.startsWith("You")) str=L("I@x1",str.substring(3)); if(target!=null) str=CMStrings.replaceAll(str,"<T-NAME>",target.name()); if(tool!=null) str=CMStrings.replaceAll(str,"<O-NAME>",tool.name()); CMLib.commands().postSay(mob,null,str,false,false); } else mob.tell(mob,target,tool,str); } protected void commonTell(MOB mob, String str) { if(mob.isMonster()&&(mob.amFollowing()!=null)) { if(str.startsWith("You")) str=L("I@x1",str.substring(3)); CMLib.commands().postSay(mob,null,str,false,false); } else mob.tell(str); } protected void commonEmote(MOB mob, String str) { if(mob.isMonster()&&(mob.amFollowing()!=null)) mob.location().show(mob,null,getActivityMessageType()|CMMsg.MASK_ALWAYS,str); else mob.tell(mob,null,null,str); } protected int lookingFor(int material, Room fromHere) { final Vector<Integer> V=new Vector<Integer>(); V.addElement(Integer.valueOf(material)); return lookingFor(V,fromHere); } protected int lookingFor(Vector<Integer> materials, Room fromHere) { final Vector<Integer> possibilities=new Vector<Integer>(); for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--) { final Room room=fromHere.getRoomInDir(d); final Exit exit=fromHere.getExitInDir(d); if((room!=null)&&(exit!=null)&&(exit.isOpen())) { final int material=room.myResource(); if(materials.contains(Integer.valueOf(material&RawMaterial.MATERIAL_MASK))) {possibilities.addElement(Integer.valueOf(d));} } } if(possibilities.size()==0) return -1; return (possibilities.elementAt(CMLib.dice().roll(1,possibilities.size(),-1))).intValue(); } public Item getRequiredFire(MOB mob,int autoGenerate) { if((autoGenerate>0) ||((this instanceof CraftingSkill)&&(!((CraftingSkill)this).fireRequired))) { if(fakeFire != null) return fakeFire; fakeFire =CMClass.getBasicItem( "StdItem" ); fakeFire.basePhyStats().setDisposition( fakeFire.basePhyStats().disposition() | PhyStats.IS_GLOWING | PhyStats.IS_LIGHTSOURCE ); fakeFire.addNonUninvokableEffect( CMClass.getAbility( "Burning" ) ); fakeFire.recoverPhyStats(); return fakeFire; } Item fire=null; for(int i=0;i<mob.location().numItems();i++) { final Item I2=mob.location().getItem(i); if((I2!=null)&&(I2.container()==null)&&(CMLib.flags().isOnFire(I2))) { fire=I2; break; } } if((fire==null)||(!mob.location().isContent(fire))) { commonTell(mob,L("A fire will need to be built first.")); return null; } return fire; } @Override public int[] usageCost(MOB mob, boolean ignoreClassOverride) { if(mob==null) return super.usageCost(null, ignoreClassOverride); if(usageType()==Ability.USAGE_NADA) return super.usageCost(mob, ignoreClassOverride); final int[][] abilityUsageCache=mob.getAbilityUsageCache(ID()); final int myCacheIndex=ignoreClassOverride?Ability.CACHEINDEX_CLASSLESS:Ability.CACHEINDEX_NORMAL; final int[] myCache=abilityUsageCache[myCacheIndex]; final boolean rebuildCache=(myCache==null); int consumed; int minimum; if(!rebuildCache && (myCache!=null )) { if(myCache.length==3) return myCache; consumed=myCache[0]; minimum=myCache[1]; } else { consumed=25; final int lvl=CMLib.ableMapper().qualifyingClassLevel(mob,this)+super.getXLOWCOSTLevel(mob); final int lowest=CMLib.ableMapper().qualifyingLevel(mob,this); final int diff=lvl-lowest; Integer[] costOverrides=null; if(!ignoreClassOverride) costOverrides=CMLib.ableMapper().getCostOverrides(mob,ID()); if(diff>0) switch(diff) { case 1: consumed = 20; break; case 2: consumed = 16; break; case 3: consumed = 13; break; case 4: consumed = 11; break; case 5: consumed = 8; break; default: consumed = 5; break; } final int maxOverride=CMProps.getMaxManaException(ID()); if(maxOverride!=Short.MIN_VALUE) { if(maxOverride<0) consumed=consumed+lowest; else if(consumed > maxOverride) consumed=maxOverride; } final int minOverride=CMProps.getMinManaException(ID()); if(minOverride!=Short.MIN_VALUE) { if(minOverride<0) consumed=(lowest<5)?5:lowest; else if(consumed<minOverride) consumed=minOverride; } if(overrideMana()>=0) consumed=overrideMana(); minimum=5; if((costOverrides!=null)&&(costOverrides[AbilityMapper.Cost.MANA.ordinal()]!=null)) { consumed=costOverrides[AbilityMapper.Cost.MANA.ordinal()].intValue(); if((consumed<minimum)&&(consumed>=0)) minimum=consumed; } } final int[] usageCost=buildCostArray(mob,consumed,minimum); if(rebuildCache) { if(consumed > COST_PCT-1) abilityUsageCache[myCacheIndex]=new int[]{consumed,minimum}; else abilityUsageCache[myCacheIndex]=usageCost; } return usageCost; } public int xlevel(MOB mob) { return mob.phyStats().level()+(2*getXLEVELLevel(mob)); } public boolean confirmPossibleMaterialLocation(int resource, Room room) { if(room==null) return false; final Integer I=Integer.valueOf(resource); final boolean isMaterial=(resource&RawMaterial.RESOURCE_MASK)==0; final int roomResourceType=room.myResource(); if(((isMaterial&&(resource==(roomResourceType&RawMaterial.MATERIAL_MASK)))) ||(I.intValue()==roomResourceType)) return true; final List<Integer> resources=room.resourceChoices(); if(resources!=null) for(int i=0;i<resources.size();i++) { if(isMaterial&&(resource==(resources.get(i).intValue()&RawMaterial.MATERIAL_MASK))) return true; else if(resources.get(i).equals(I)) return true; } return false; } public Integer[] supportedResourcesMap() { final String rscs=supportedResourceString().toUpperCase(); if(resourcesMap.containsKey(rscs)) { return resourcesMap.get(rscs); } else { final List<String> set=CMParms.parseAny(supportedResourceString(),"|",true); final List<Integer> finalSet=new ArrayList<Integer>(); for(int i=0;i<set.size();i++) { int x=-1; String setMat=set.get(i); if(setMat.startsWith("_")) x=RawMaterial.CODES.FIND_IgnoreCase(setMat.substring(1)); else { final int y=setMat.indexOf('-'); List<String> restV=null; if(y>0) { restV=CMParms.parseAny(setMat.substring(y+1),"-", true); setMat=setMat.substring(0, y); } final RawMaterial.Material m=RawMaterial.Material.findIgnoreCase(setMat); if(m!=null) { x=m.mask(); if((restV!=null)&&(restV.size()>0)) { final List<Integer> rscsV=new XVector<Integer>(RawMaterial.CODES.COMPOSE_RESOURCES(x)); for(final String sv : restV) { final int code = RawMaterial.CODES.FIND_CaseSensitive(sv); if(code >=0) rscsV.remove(Integer.valueOf(code)); } for(int codeDex=0;codeDex<rscsV.size()-1;codeDex++) finalSet.add(rscsV.get(codeDex)); x=rscsV.get(rscsV.size()-1).intValue(); } } } if(x<0) x=RawMaterial.CODES.FIND_IgnoreCase(setMat); if(x>=0) finalSet.add(Integer.valueOf(x)); } final Integer[] finalArray=finalSet.toArray(new Integer[0]); resourcesMap.put(rscs, finalArray); return finalArray; } } public boolean isMadeOfSupportedResource(Item I) { if(I==null) return false; for(final Integer R : supportedResourcesMap()) { if((R.intValue() & RawMaterial.MATERIAL_MASK)==0) { if((I.material()& RawMaterial.MATERIAL_MASK)==R.intValue()) return true; } else if(I.material()==R.intValue()) return true; } return false; } @Override public boolean canBeLearnedBy(MOB teacherM, MOB studentM) { if(!super.canBeLearnedBy(teacherM,studentM)) return false; if(studentM==null) return true; final CharClass C=studentM.charStats().getCurrentClass(); if(CMLib.ableMapper().getQualifyingLevel(C.ID(), false, ID())>=0) return true; final boolean crafting = ((classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_CRAFTINGSKILL); final AbilityComponents.AbilityLimits remainders = CMLib.ableComponents().getCommonSkillRemainder(studentM, this); if(remainders.commonSkills()<=0) { teacherM.tell(L("@x1 can not learn any more common skills.",studentM.name(teacherM))); studentM.tell(L("You have learned the maximum @x1 common skills, and may not learn any more.",""+remainders.maxCommonSkills())); return false; } if(remainders.specificSkillLimit()<=0) { if(crafting) teacherM.tell(L("@x1 can not learn any more crafting common skills.",studentM.name(teacherM))); else teacherM.tell(L("@x1 can not learn any more non-crafting common skills.",studentM.name(teacherM))); final int max = crafting ? remainders.maxCraftingSkills() : remainders.maxNonCraftingSkills(); if(crafting) studentM.tell(L("You have learned the maximum @x1 crafting skills, and may not learn any more.",""+max)); else studentM.tell(L("You have learned the maximum @x1 non-crafting skills, and may not learn any more.",""+max)); return false; } return true; } @Override public void teach(MOB teacher, MOB student) { super.teach(teacher, student); if((student!=null)&&(student.fetchAbility(ID())!=null)) { final CharClass C=student.charStats().getCurrentClass(); if(CMLib.ableMapper().getQualifyingLevel(C.ID(), false, ID())>=0) return; final boolean crafting = ((classificationCode()&Ability.ALL_DOMAINS)==Ability.DOMAIN_CRAFTINGSKILL); final AbilityComponents.AbilityLimits remainders = CMLib.ableComponents().getCommonSkillRemainder(student, this); if(remainders.commonSkills()<=0) student.tell(L("@x1 may not learn any more common skills.",student.name())); else if(remainders.commonSkills()<=Integer.MAX_VALUE/2) student.tell(L("@x1 may learn @x2 more common skills.",student.name(),""+remainders.commonSkills())); if(remainders.specificSkillLimit()<=0) student.tell(L("@x1 may not learn any more @x2crafting common skills.",student.name(),(crafting?"":"non-"))); else if(remainders.specificSkillLimit()<=Integer.MAX_VALUE/2) student.tell(L("@x1 may learn @x2 more @x3crafting common skills.",student.name(),""+remainders.specificSkillLimit(),(crafting?"":"non-"))); } } public void bumpTickDown(long byThisMuch) { tickDown+=byThisMuch; if(byThisMuch > 0) this.lastBaseDuration+=byThisMuch; } @Override public void startTickDown(MOB invokerMOB, Physical affected, int tickTime) { super.startTickDown(invokerMOB, affected, tickTime); tickUp=0; } public boolean checkStop(MOB mob, List<String> commands) { if((commands!=null) &&(commands.size()==1) &&(commands.get(0).equalsIgnoreCase("stop"))) { final Ability A=mob.fetchEffect(ID()); if((A!=null)&&(!A.isNowAnAutoEffect())&&(A.canBeUninvoked())) { if(A instanceof CommonSkill) ((CommonSkill)A).aborted=true; A.unInvoke(); return true; } mob.tell(L("You are not doing that right now.")); } return false; } @Override public void setMiscText(String newMiscText) { if("abort".equalsIgnoreCase(newMiscText)) { this.aborted=true; this.tickDown=1; this.tick(affected, Tickable.TICKID_MOB); } else super.setMiscText(newMiscText); } @Override public boolean invoke(MOB mob, List<String> commands, Physical givenTarget, boolean auto, int asLevel) { aborted=false; if(mob.isInCombat()) { commonEmote(mob,L("<S-NAME> <S-IS-ARE> in combat!")); return false; } if((!allowedWhileMounted())&&(mob.riding()!=null)) { commonEmote(mob,L("You can't do that while @x1 @x2.",mob.riding().stateString(mob),mob.riding().name())); return false; } if((!allowedInTheDark())&&(!CMLib.flags().canBeSeenBy(mob.location(),mob))) { commonTell(mob,L("<S-NAME> can't see to do that!")); return false; } if((CMLib.flags().isSitting(mob)&&(!canBeDoneSittingDown()))||CMLib.flags().isSleeping(mob)) { commonTell(mob,L("You need to stand up!")); return false; } for(final Enumeration<Ability> a=mob.personalEffects();a.hasMoreElements();) { final Ability A=a.nextElement(); if((A!=null) &&(((A.classificationCode()&Ability.ALL_ACODES)==Ability.ACODE_COMMON_SKILL)||(A.ID().equalsIgnoreCase("AstroEngineering"))) &&(!getUninvokeException().contains(A.ID()))) { if(A instanceof CommonSkill) ((CommonSkill)A).aborted=true; A.unInvoke(); } } isAnAutoEffect=false; // if you can't move, you can't do anything! if(!CMLib.flags().isAliveAwakeMobileUnbound(mob,false)) return false; final int[] consumed=usageCost(mob,false); if(mob.curState().getMana()<consumed[Ability.USAGEINDEX_MANA]) { if(mob.maxState().getMana()==consumed[Ability.USAGEINDEX_MANA]) mob.tell(L("You must be at full mana to do that.")); else mob.tell(L("You don't have enough mana to do that.")); return false; } if(mob.curState().getMovement()<consumed[Ability.USAGEINDEX_MOVEMENT]) { if(mob.maxState().getMovement()==consumed[Ability.USAGEINDEX_MOVEMENT]) mob.tell(L("You must be at full movement to do that.")); else mob.tell(L("You don't have enough movement to do that. You are too tired.")); return false; } if(mob.curState().getHitPoints()<consumed[Ability.USAGEINDEX_HITPOINTS]) { if(mob.maxState().getHitPoints()==consumed[Ability.USAGEINDEX_HITPOINTS]) mob.tell(L("You must be at full health to do that.")); else mob.tell(L("You don't have enough hit points to do that.")); return false; } if(!checkComponents(mob)) return false; mob.curState().adjMana(-consumed[0],mob.maxState()); mob.curState().adjMovement(-consumed[1],mob.maxState()); mob.curState().adjHitPoints(-consumed[2],mob.maxState()); setAbilityCode(baseYield()); activityRoom=mob.location(); if(!bundling) helpProficiency(mob, 0); return true; } }