001package com.fs.starfarer.api.impl.campaign.rulecmd;
002
003import java.util.ArrayList;
004import java.util.LinkedHashSet;
005import java.util.List;
006import java.util.Map;
007import java.util.Random;
008import java.util.Set;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.BaseCustomProductionPickerDelegateImpl;
012import com.fs.starfarer.api.campaign.CampaignFleetAPI;
013import com.fs.starfarer.api.campaign.CargoAPI;
014import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
015import com.fs.starfarer.api.campaign.FactionAPI;
016import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
017import com.fs.starfarer.api.campaign.FactionAPI.ShipPickParams;
018import com.fs.starfarer.api.campaign.FactionProductionAPI;
019import com.fs.starfarer.api.campaign.FactionProductionAPI.ItemInProductionAPI;
020import com.fs.starfarer.api.campaign.FactionProductionAPI.ProductionItemType;
021import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin.DataForEncounterSide;
022import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin.FleetMemberData;
023import com.fs.starfarer.api.campaign.InteractionDialogAPI;
024import com.fs.starfarer.api.campaign.OptionPanelAPI;
025import com.fs.starfarer.api.campaign.SectorEntityToken;
026import com.fs.starfarer.api.campaign.SpecialItemData;
027import com.fs.starfarer.api.campaign.SpecialItemPlugin.RightClickActionHelper;
028import com.fs.starfarer.api.campaign.TextPanelAPI;
029import com.fs.starfarer.api.campaign.impl.items.ShroudedHullmodItemPlugin;
030import com.fs.starfarer.api.campaign.impl.items.ShroudedSubstratePlugin;
031import com.fs.starfarer.api.campaign.rules.MemKeys;
032import com.fs.starfarer.api.campaign.rules.MemoryAPI;
033import com.fs.starfarer.api.combat.BattleCreationContext;
034import com.fs.starfarer.api.combat.ShipVariantAPI;
035import com.fs.starfarer.api.fleet.FleetMemberAPI;
036import com.fs.starfarer.api.fleet.ShipRolePick;
037import com.fs.starfarer.api.impl.campaign.AbyssalLightEntityPlugin;
038import com.fs.starfarer.api.impl.campaign.AbyssalLightEntityPlugin.DespawnType;
039import com.fs.starfarer.api.impl.campaign.FleetEncounterContext;
040import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl;
041import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.BaseFIDDelegate;
042import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.FIDConfig;
043import com.fs.starfarer.api.impl.campaign.RuleBasedInteractionDialogPluginImpl;
044import com.fs.starfarer.api.impl.campaign.ids.Factions;
045import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
046import com.fs.starfarer.api.impl.campaign.ids.Items;
047import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
048import com.fs.starfarer.api.impl.campaign.ids.ShipRoles;
049import com.fs.starfarer.api.impl.campaign.ids.Tags;
050import com.fs.starfarer.api.loading.HullModSpecAPI;
051import com.fs.starfarer.api.loading.WeaponSpecAPI;
052import com.fs.starfarer.api.util.ListMap;
053import com.fs.starfarer.api.util.Misc;
054import com.fs.starfarer.api.util.Misc.Token;
055import com.fs.starfarer.api.util.WeightedRandomPicker;
056
057/**
058 */
059public class DwellerCMD extends BaseCommandPlugin {
060        
061        public static enum DwellerStrength {
062                LOW,
063                MEDIUM,
064                HIGH,
065                EXTREME,
066        }
067        
068        public static String SHROUDED_TENDRIL = "shrouded_tendril";
069        public static String SHROUDED_EYE = "shrouded_eye";
070        public static String SHROUDED_MAELSTROM = "shrouded_maelstrom";
071        public static String SHROUDED_MAW = "shrouded_maw";
072        
073
074        public static ListMap<String> GUARANTEED_FIRST_TIME_ITEMS = new ListMap<>();
075        static {
076                GUARANTEED_FIRST_TIME_ITEMS.add(SHROUDED_EYE, Items.SHROUDED_LENS);
077                GUARANTEED_FIRST_TIME_ITEMS.add(SHROUDED_MAELSTROM, Items.SHROUDED_THUNDERHEAD);
078                GUARANTEED_FIRST_TIME_ITEMS.add(SHROUDED_MAW, Items.SHROUDED_MANTLE);
079        }
080        
081        public static ListMap<String> DROP_GROUPS = new ListMap<>();
082        static {
083                DROP_GROUPS.add(SHROUDED_EYE, "drops_shrouded_eye");
084                DROP_GROUPS.add(SHROUDED_MAELSTROM, "drops_shrouded_maelstrom");
085                DROP_GROUPS.add(SHROUDED_MAW, "drops_shrouded_maw");
086        }
087        
088        
089
090        public boolean execute(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
091                if (dialog == null) return false;
092                
093                OptionPanelAPI options = dialog.getOptionPanel();
094                TextPanelAPI text = dialog.getTextPanel();
095                CampaignFleetAPI pf = Global.getSector().getPlayerFleet();
096                CargoAPI cargo = pf.getCargo();
097                
098                final SectorEntityToken entity = dialog.getInteractionTarget();
099                long seed = Misc.getSalvageSeed(entity);
100                Random random = Misc.getRandom(seed, 11);
101                //random = new Random();
102                
103                String action = params.get(0).getString(memoryMap);
104                
105                MemoryAPI memory = memoryMap.get(MemKeys.LOCAL);
106                if (memory == null) return false; // should not be possible unless there are other big problems already
107                                
108                if ("smallFleet".equals(action)) {
109                        return engageFleet(dialog, memoryMap, memory, DwellerStrength.LOW, random);
110                } else if ("mediumFleet".equals(action)) {
111                        return engageFleet(dialog, memoryMap, memory, DwellerStrength.MEDIUM, random);
112                } else if ("largeFleet".equals(action)) {
113                        return engageFleet(dialog, memoryMap, memory, DwellerStrength.HIGH, random);
114                } else if ("hugeFleet".equals(action)) {
115                        return engageFleet(dialog, memoryMap, memory, DwellerStrength.EXTREME, random);
116                } else if ("showWeaponPicker".equals(action)) {
117                        showWeaponPicker(dialog, memoryMap);
118                        return true;
119                } else if ("unlockHullmod".equals(action)) {
120                        unlockHullmod(dialog, memoryMap);
121                        return true;
122                }
123                return false;
124        }
125
126        protected void unlockHullmod(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
127                String modId = Global.getSector().getPlayerMemoryWithoutUpdate().getString(
128                                                        ShroudedHullmodItemPlugin.SHROUDED_HULLMOD_ID);
129                HullModSpecAPI modSpec = Global.getSettings().getHullModSpec(modId);
130                
131                Global.getSoundPlayer().playUISound("ui_acquired_hullmod", 1, 1);
132                TextPanelAPI text = dialog.getTextPanel();
133                text.setFontSmallInsignia();
134                String str = modSpec.getDisplayName(); 
135                text.addParagraph("Acquired hull mod: " + str + "", Misc.getPositiveHighlightColor());
136                text.highlightInLastPara(Misc.getHighlightColor(), str);
137                text.setFontInsignia();
138                
139//              Global.getSector().getCampaignUI().getMessageDisplay().addMessage(
140//                              "Acquired hull mod: " + modSpec.getDisplayName() + "");
141                
142                Global.getSector().getPlayerFaction().addKnownHullMod(modId);;
143        }
144        
145        public static int getSubstrateCost(WeaponSpecAPI spec) {
146                if (!spec.hasTag(Tags.DWELLER)) return 0;
147                String substrate = "substrate_";
148                for (String tag : spec.getTags()) {
149                        if (tag.startsWith(substrate)) {
150                                String num = tag.replaceFirst(substrate, "");
151                                return Integer.parseInt(num);
152                        }
153                }
154                return 0;
155        }
156        
157        protected void showWeaponPicker(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
158                
159                int substrate = Global.getSector().getPlayerMemoryWithoutUpdate().getInt(ShroudedSubstratePlugin.SHROUDED_SUBSTRATE_AVAILABLE);
160                
161                Set<String> weapons = new LinkedHashSet<>();
162                for (WeaponSpecAPI spec : Global.getSettings().getAllWeaponSpecs()) {
163                        int cost = getSubstrateCost(spec);
164                        if (cost > 0 && cost <= substrate) {
165                                weapons.add(spec.getWeaponId());
166                        }
167                }
168                
169                dialog.showCustomProductionPicker(new BaseCustomProductionPickerDelegateImpl() {
170                        @Override
171                        public Set<String> getAvailableFighters() {
172                                return new LinkedHashSet<>();
173                        }
174                        @Override
175                        public Set<String> getAvailableShipHulls() {
176                                return new LinkedHashSet<>();
177                        }
178                        @Override
179                        public Set<String> getAvailableWeapons() {
180                                return weapons;
181                        }
182                        @Override
183                        public float getCostMult() {
184                                return 1f;
185                        }
186                        @Override
187                        public float getMaximumValue() {
188                                return substrate;
189                        }
190                        
191                        @Override
192                        public String getWeaponColumnNameOverride() {
193                                return "Weapon";
194                        }
195
196                        @Override
197                        public String getNoMatchingBlueprintsLabelOverride() {
198                                return "No matching weapons";
199                        }
200
201                        @Override
202                        public String getMaximumOrderValueLabelOverride() {
203                                return "Shrouded Substrate available";
204                        }
205
206                        @Override
207                        public String getCurrentOrderValueLabelOverride() {
208                                return "Shrouded Substrate required";
209                        }
210                        @Override
211                        public String getItemGoesOverMaxValueStringOverride() {
212                                return "Not enough Shrouded Substrate";
213                        }
214                        @Override
215                        public String getCustomOrderLabelOverride() {
216                                return "Weapon assembly";
217                        }
218                        @Override
219                        public String getNoProductionOrdersLabelOverride() {
220                                return "No assembly orders";
221                        }
222                        @Override
223                        public boolean withQuantityLimits() {
224                                return false;
225                        }
226                        @Override
227                        public boolean isUseCreditSign() {
228                                return false;
229                        }
230
231                        @Override
232                        public int getCostOverride(Object item) {
233                                if (item instanceof WeaponSpecAPI) {
234                                        return getSubstrateCost((WeaponSpecAPI) item);
235                                }
236                                return -1;
237                        }
238                        
239                        @Override
240                        public void notifyProductionSelected(FactionProductionAPI production) {
241                                if (!(dialog.getPlugin() instanceof RuleBasedInteractionDialogPluginImpl)) return;
242                                RuleBasedInteractionDialogPluginImpl plugin = (RuleBasedInteractionDialogPluginImpl) dialog.getPlugin();
243                                if (!(plugin.getCustom1() instanceof RightClickActionHelper)) return;
244                                RightClickActionHelper helper = (RightClickActionHelper) plugin.getCustom1();
245                                
246                                int cost = production.getTotalCurrentCost();
247                                helper.removeFromClickedStackFirst(cost);
248                                int substrate = (int) helper.getNumItems(CargoItemType.SPECIAL, new SpecialItemData(Items.SHROUDED_SUBSTRATE, null));
249                                Global.getSector().getPlayerMemoryWithoutUpdate().set(ShroudedSubstratePlugin.SHROUDED_SUBSTRATE_AVAILABLE, substrate);
250                                
251                                for (ItemInProductionAPI item : production.getCurrent()) {
252                                        if (item.getType() == ProductionItemType.WEAPON) {
253                                                helper.addItems(CargoItemType.WEAPONS, item.getSpecId(), item.getQuantity());
254                                                AddRemoveCommodity.addWeaponGainText(item.getSpecId(), item.getQuantity(), dialog.getTextPanel());
255                                        }
256                                }
257                                
258                                FireBest.fire(null, dialog, memoryMap, "SubstrateWeaponsPicked");
259                                
260                                Global.getSoundPlayer().playUISound("ui_cargo_machinery_drop", 1f, 1f);
261                        }
262                });
263        }
264
265        
266        protected boolean engageFleet(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap, MemoryAPI memory, DwellerStrength str, Random random) {
267                CampaignFleetAPI fleet = createDwellerFleet(str, random);
268                if (fleet == null) return false;
269                
270                CampaignFleetAPI pf = Global.getSector().getPlayerFleet();
271                fleet.setContainingLocation(pf.getContainingLocation());
272                
273                final SectorEntityToken entity = dialog.getInteractionTarget();
274                
275                dialog.setInteractionTarget(fleet);
276                
277                Global.getSector().getCampaignUI().restartEncounterMusic(fleet);
278                
279                FIDConfig config = new FIDConfig();
280                
281                config.delegate = new BaseFIDDelegate() {
282                        public void postPlayerSalvageGeneration(InteractionDialogAPI dialog, FleetEncounterContext context, CargoAPI salvage) {
283                                if (!(dialog.getInteractionTarget() instanceof CampaignFleetAPI)) return;
284                                
285                                float mult = context.computePlayerContribFraction();
286                                
287                                CampaignFleetAPI fleet = (CampaignFleetAPI) dialog.getInteractionTarget();
288
289                                DataForEncounterSide data = context.getDataFor(fleet);
290                                List<FleetMemberAPI> losses = new ArrayList<FleetMemberAPI>();
291                                for (FleetMemberData fmd : data.getOwnCasualties()) {
292                                        losses.add(fmd.getMember());
293                                }
294                                
295                                float min = 0f;
296                                float max = 0f;
297                                boolean gotGuaranteed = false;
298                                for (FleetMemberAPI member : losses) {
299                                        if (member.getHullSpec().hasTag(Tags.DWELLER)) {
300                                                String key = "substrate_";
301                                                float [] sDrops = Misc.getFloatArray(key + member.getHullSpec().getHullId());
302                                                if (sDrops == null) {
303                                                        sDrops = Misc.getFloatArray(key + member.getHullSpec().getHullSize().name());
304                                                }
305                                                if (sDrops == null) continue;
306                                                
307                                                min += sDrops[0];
308                                                max += sDrops[1];
309                                                
310                                                String hullId = member.getHullSpec().getRestoredToHullId();
311                                                String defeatedKey = "$defeatedDweller_" + hullId;
312                                                boolean firstTime = !Global.getSector().getPlayerMemoryWithoutUpdate().getBoolean(defeatedKey);
313                                                Global.getSector().getPlayerMemoryWithoutUpdate().set(defeatedKey, true);
314                                                if (firstTime && !gotGuaranteed) {
315                                                        List<String> drops = GUARANTEED_FIRST_TIME_ITEMS.get(hullId);
316                                                        for (String itemId : drops) {
317                                                                SpecialItemData sid = new SpecialItemData(itemId, null);
318                                                                boolean add = firstTime && salvage.getQuantity(CargoItemType.SPECIAL, sid) <= 0;
319                                                                if (add) {
320                                                                        salvage.addItems(CargoItemType.SPECIAL, sid, 1);
321                                                                        gotGuaranteed = true;
322                                                                }
323                                                        }
324                                                }
325                                        }
326                                }
327                                
328                                long seed = Misc.getSalvageSeed(entity);
329                                Random random = Misc.getRandom(seed, 50);
330                                int substrate = 0;
331                                if (min + max < 1f) {
332                                        if (random.nextFloat() < (min + max) / 2f) {
333                                                substrate = 1;
334                                        }
335                                } else {
336                                        substrate = (int) Math.round(min + (max - min) * random.nextFloat());
337                                }
338                                
339                                if (substrate > 0) {
340                                        salvage.addItems(CargoItemType.SPECIAL, new SpecialItemData(Items.SHROUDED_SUBSTRATE, null), substrate);
341                                }
342                        }
343                        
344                        public void battleContextCreated(InteractionDialogAPI dialog, BattleCreationContext bcc) {
345                                bcc.aiRetreatAllowed = false;
346                                bcc.fightToTheLast = true;
347                                bcc.objectivesAllowed = false;
348                                bcc.enemyDeployAll = true;
349                                
350                                // despawn the light here - the salvage gen method is only called if the player won
351                                // but want to despawn the light after any fight, regardless
352                                if (entity.getCustomPlugin() instanceof AbyssalLightEntityPlugin) {
353                                        AbyssalLightEntityPlugin plugin = (AbyssalLightEntityPlugin) entity.getCustomPlugin();
354                                        plugin.despawn(DespawnType.FADE_OUT);
355                                }
356                        }
357                };
358                
359                config.alwaysAttackVsAttack = true;
360                //config.alwaysPursue = true;
361                config.alwaysHarry = true;
362                config.showTransponderStatus = false;
363                //config.showEngageText = false;
364                config.lootCredits = false;             
365                
366                config.showCommLinkOption = false;
367                config.showEngageText = false;
368                config.showFleetAttitude = false;
369                config.showTransponderStatus = false;
370                config.showWarningDialogWhenNotHostile = false;
371                config.impactsAllyReputation = false;
372                config.impactsEnemyReputation = false;
373                config.pullInAllies = false;
374                config.pullInEnemies = false;
375                config.pullInStations = false;
376                
377                config.showCrRecoveryText = false;
378                config.firstTimeEngageOptionText = "\"Battle stations!\"";
379                config.afterFirstTimeEngageOptionText = "Move in to re-engage";
380                
381                if (str == DwellerStrength.LOW) {
382                        config.firstTimeEngageOptionText = null;
383                        config.leaveAlwaysAvailable = true;
384                } else {
385                        config.leaveAlwaysAvailable = true; // except for first engagement
386                        config.noLeaveOptionOnFirstEngagement = true;
387                }
388                //config.noLeaveOption = true;
389                
390//              config.noSalvageLeaveOptionText = "Continue";
391                
392//              config.dismissOnLeave = false;
393//              config.printXPToDialog = true;
394                
395                long seed = Misc.getSalvageSeed(entity);
396                config.salvageRandom = Misc.getRandom(seed, 75);
397                
398                Global.getSector().getPlayerMemoryWithoutUpdate().set("$encounteredDweller", true);
399                Global.getSector().getPlayerMemoryWithoutUpdate().set("$encounteredMonster", true);
400                Global.getSector().getPlayerMemoryWithoutUpdate().set("$encounteredWeird", true);
401                
402                final FleetInteractionDialogPluginImpl plugin = new FleetInteractionDialogPluginImpl(config);
403                
404                //final InteractionDialogPlugin originalPlugin = dialog.getPlugin();
405                
406                dialog.setPlugin(plugin);
407                plugin.init(dialog);
408                
409                
410                return true;
411        }
412
413
414
415        public static CampaignFleetAPI createDwellerFleet(DwellerStrength str, Random random) {
416                CampaignFleetAPI f = Global.getFactory().createEmptyFleet(Factions.DWELLER, "Manifestation", true);
417                
418                FactionAPI faction = Global.getSector().getFaction(Factions.DWELLER);
419                String typeKey = FleetTypes.PATROL_SMALL;
420                if (str == DwellerStrength.MEDIUM) typeKey = FleetTypes.PATROL_MEDIUM; 
421                if (str == DwellerStrength.HIGH) typeKey = FleetTypes.PATROL_LARGE; 
422                if (str == DwellerStrength.EXTREME) typeKey = FleetTypes.PATROL_LARGE; 
423                f.setName(faction.getFleetTypeName(typeKey));
424                
425                f.setInflater(null);
426                
427                if (str == DwellerStrength.LOW) {
428                        addShips(f, 6, 8, random, ShipRoles.DWELLER_TENDRIL);
429                        addShips(f, 1, 1, random, ShipRoles.DWELLER_EYE);
430                        addShips(f, 1, 2, random, ShipRoles.DWELLER_MAELSTROM);
431                } else if (str == DwellerStrength.MEDIUM) {
432                        addShips(f, 9, 12, random, ShipRoles.DWELLER_TENDRIL);
433                        int eyes = addShips(f, 1, 1, random, ShipRoles.DWELLER_EYE);
434                        addShips(f, 2 - eyes, 3 - eyes, random, ShipRoles.DWELLER_MAELSTROM);
435                        addShips(f, 1, 1, random, ShipRoles.DWELLER_MAW);
436                } else if (str == DwellerStrength.HIGH) {
437                        addShips(f, 11, 14, random, ShipRoles.DWELLER_TENDRIL);
438                        int eyes = addShips(f, 2, 3, random, ShipRoles.DWELLER_EYE);
439                        addShips(f, 3 - eyes, 5 - eyes, random, ShipRoles.DWELLER_MAELSTROM);
440                        addShips(f, 1, 1, random, ShipRoles.DWELLER_MAW);
441                } else if (str == DwellerStrength.EXTREME) {
442                        addShips(f, 12, 15, random, ShipRoles.DWELLER_TENDRIL);
443                        int eyes = addShips(f, 2, 3, random, ShipRoles.DWELLER_EYE);
444                        addShips(f, 3 - eyes, 5 - eyes, random, ShipRoles.DWELLER_MAELSTROM);
445                        addShips(f, 2, 2, random, ShipRoles.DWELLER_MAW);
446                }
447                
448                f.getFleetData().setSyncNeeded();
449                f.getFleetData().syncIfNeeded();
450                f.getFleetData().sort();
451                
452                for (FleetMemberAPI curr : f.getFleetData().getMembersListCopy()) {
453                        curr.getRepairTracker().setCR(curr.getRepairTracker().getMaxCR());
454                        
455                        // tag is added to ships now
456//                      ShipVariantAPI v = curr.getVariant().clone();
457//                      v.addTag(Tags.LIMITED_TOOLTIP_IF_LOCKED);
458//                      curr.setVariant(v, false, false);
459                }
460                
461                
462//              f.getMemoryWithoutUpdate().set(MemFlags.FLEET_INTERACTION_DIALOG_CONFIG_OVERRIDE_GEN, 
463//                                                      new DwellerFIDConfig());
464//              f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_AGGRESSIVE, true);
465                
466                // required for proper music track to play, see: DwellerCMD
467                f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_HOSTILE, true);
468                
469//              //f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_ALWAYS_PURSUE, true);
470//              f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_ALLOW_LONG_PURSUIT, true);
471                f.getMemoryWithoutUpdate().set(MemFlags.MAY_GO_INTO_ABYSS, true);
472                
473                return f;
474        }
475        
476        
477        public static int addShips(CampaignFleetAPI fleet, int min, int max, Random random, Object ... roles) {
478                if (min < 0) min = 0;
479                if (max < 0) max = 0;
480                
481                WeightedRandomPicker<String> picker = new WeightedRandomPicker<>();
482                if (roles.length == 1) {
483                        picker.add((String) roles[0], 1f);
484                } else {
485                        for (int i = 0; i < roles.length; i += 2) {
486                                picker.add((String) roles[i], (float) roles[i + 1]);
487                        }
488                }
489                int num = min + random.nextInt(max - min + 1);
490                FactionAPI faction = Global.getSector().getFaction(Factions.DWELLER);
491                
492                ShipPickParams p = new ShipPickParams(ShipPickMode.ALL);
493                p.blockFallback = true;
494                p.maxFP = 1000000;
495                for (int i = 0; i < num; i++) {
496                        String role = picker.pick();
497                        List<ShipRolePick> picks = faction.pickShip(role, p, null, random);
498                        for (ShipRolePick pick : picks) {
499                                fleet.getFleetData().addFleetMember(pick.variantId);
500                                
501                                ShipVariantAPI variant = Global.getSettings().getVariant(pick.variantId);
502                                if (variant != null) {
503                                        String hullId = variant.getHullSpec().getRestoredToHullId();
504                                        List<String> dropGroups = DROP_GROUPS.get(hullId);
505                                        for (String group : dropGroups) {
506                                                fleet.addDropRandom(group, 1);
507                                        }
508                                }
509                        }
510                }
511                return num;
512        }
513}
514