示例#1
0
    def _compatibility(self, unitobj, tested=None):
        # print(f"{self.name} testing against {unit.id} {unit.name}")
        if tested is None:
            tested = []
        if unitobj.uniqueid in tested:
            return True

        for r in self.reqs:
            if not r.test(unitobj):
                return False

        # These need only be true for the first shape in the collection
        # ... except I'm getting bugs from this
        if self.landok:
            if unitobj.aquatic != -1:
                return False

        if self.uwok:
            if unitobj.aquatic != -1 and unitobj.amphibian != -1 and unitobj.pooramphibian != -1:
                return False

        if self.reqmage and len(tested) == 0:
            haspaths = False
            for path in ["F", "A", "W", "E", "S", "D", "N", "B"]:
                if hasattr(unitobj, path) and getattr(unitobj, path, 0) > 0:
                    haspaths = True
                    break
            if not haspaths:
                return False

        # I cannot support shrink or growhp
        if unitobj.shrinkhp != -1:
            return False
        if unitobj.growhp != -1:
            return False

        wpnmod = utils.weaponmods.get(self.weaponmod,
                                      utils.weaponmods.get("Do Nothing"))
        if not wpnmod.compatibilityunit(unitobj):
            return False
        tested.append(unitobj.uniqueid)

        # Must recursively check for eligibility on all subshapes too
        for x in [
                "shapechange", "firstshape", "secondshape", "secondtmpshape",
                "forestshape", "plainshape", "foreignshape", "homeshape",
                "domshape", "notdomshape", "springshape", "summershape",
                "autumnshape", "wintershape", "landshape", "watershape"
        ]:
            if hasattr(unitobj, x):
                uid = getattr(unitobj, x)
                if uid not in tested and uid > 0:
                    unittottest = unitinbasedatafinder.get(uid)
                    if not self._compatibility(unittottest, tested=tested):
                        return False

        return True
示例#2
0
    def toUnitBaseData(self) -> unitinbasedatafinder.UnitInBaseDataFinder:
        #self._init()
        if self.baseunit is not None:
            retval = unitinbasedatafinder.get(self.baseunit)
            if self.clearweapons != 0:
                retval.weapons = []
        else:
            retval = unitinbasedatafinder.UnitInBaseDataFinder()

        for wpnid in self.addweapons:
            retval.weapons.append(weapon.get(wpnid))

        for attrib, value in self.setcmds:
            setattr(retval, attrib, value)

        retval.additionalmodcmds += f"\n-- Generated from NewUnit {self.name}\n"
        retval.additionalmodcmds += "\n".join(self.rawcmds)
        retval.additionalmodcmds += "\n"
        retval.uniqueid = f"newunit-{self.name}"

        return retval
示例#3
0
def generateUnit(parentobj, numtogenerate, spell, secondaryeffect,
                 actualpowerlvl):
    # When dealing with nextspell chains, we need to update #details on the first one (the one the players see)
    firstspell = spell
    while True:
        if firstspell.prevspell is None:
            break
        firstspell = firstspell.prevspell
    output = ""
    montag = MontagBuilder()
    montag.makedummymonster = parentobj.makedummymonster
    montag.makebattledummymonster = parentobj.makebattledummymonster
    dummynames = parentobj.dummymonsternames.get(spell.path1, [])
    if len(dummynames) > 0:
        montag.dummymonstername = naming.parsestring(random.choice(dummynames))
    for n in range(0, numtogenerate):

        # Find a unit to use
        unittouse = None
        effectpool = []
        if parentobj.usefixedunitid > 0:
            unittouse = unitinbasedatafinder.get(parentobj.usefixedunitid)
            minnreff = 1
            costper = 1.0
            chosensummoneffect = None
            # Unitmod
        elif parentobj.selectunitmods is not None and len(
                parentobj.selectunitmods) > 0:
            for selectunitmodname in parentobj.selectunitmods:
                realunitmod = utils.unitmods[selectunitmodname]
                # this unit mod should instead be a picker for a unit to grab
                # start by building a pool of spelleffects that we could use

                for effname, effect in utils.spelleffects.items():
                    if effect.effect in parentobj.effectnumberforunits:
                        unitid = effect.damage
                        # no montags
                        if unitid < 0:
                            continue
                        #unitobj = unitinbasedatafinder.get(unitid)

                        if parentobj.restrictunitstospellpaths > 0:
                            if not (effect.paths & spell.path1):
                                continue
                            if effect.secondarypathchance >= 85 and not (
                                    effect.secondarypaths & spell.path2):
                                continue

                        if realunitmod.compatibilityWithSpellEffect(effect):
                            effectpool.append(effect)
            random.shuffle(effectpool)

        generateokay = False
        # Loop to deal with deadend failures
        # this is typically a bad unit choice for which there is no way to adjust
        # the power up/down with legal secondaries
        secondary = None

        print(
            f"Spell paths for this generation are {spell.path1} and {spell.path2}"
        )
        print(
            f"Effect pool contains {len(effectpool)} effects from {parentobj.selectunitmods}"
        )

        while 1:
            if len(effectpool) > 0:
                chosensummoneffect = effectpool.pop(0)
                print(
                    f"Consider effect {chosensummoneffect}, {len(effectpool)} remain after this "
                )
                unittouse = chosensummoneffect.getUnit()
                if chosensummoneffect.damage < 0:
                    print(
                        f"Discard effect {chosensummoneffect}: it makes montag {chosensummoneffect.damage}"
                    )
                    continue

            elif unittouse is None:
                print(
                    f"ERROR: {parentobj.name} with paths {spell.path1} and {spell.path2} found no valid unit summon"
                )
                return None

            if (parentobj.unitmodlist is not None or len(
                    parentobj.allowedunitmods) > 0) and unittouse is not None:
                # If rollSpell enforces a secondary effect (unlikely), use that
                if secondaryeffect is not None and secondaryeffect.name != "Do Nothing" and len(
                        secondaryeffect.unitmod) > 0:
                    realunitmod = utils.unitmods[secondaryeffect.unitmod]
                    unitobj = unittouse
                    secondary = utils.unitmodToSecondary(realunitmod,
                                                         fallback=True)
                    if not _CanUseUnitAndSecondaryCombo(
                            parentobj, unitobj, secondary, realunitmod, spell,
                            chosensummoneffect, actualpowerlvl):
                        print(
                            f"Forced unitmod {realunitmod.name} not allowed with unit {unittouse}"
                        )
                        unittouse = None
                        continue
                    output = f"-- {parentobj.name} applied secondary effect unitmod {realunitmod.name} " \
                             f"to {unittouse.uniqueid}\n\n" + output
                else:
                    # Find a secondary effect to use for this creature
                    if parentobj.secondaryeffectchance is not None and random.random(
                    ) * 100 > parentobj.secondaryeffectchance:
                        realunitmod = utils.unitmods["Do Nothing"]
                        secondary = utils.unitmodToSecondary(realunitmod,
                                                             fallback=True)
                    else:
                        # shallow copy
                        unitmodlist = parentobj.allowedunitmods[:]
                        if parentobj.unitmodlist is not None:
                            unitmodlist += utils.unitmodlists[
                                parentobj.unitmodlist]
                        random.shuffle(unitmodlist)
                        bad = False
                        while 1:
                            if len(unitmodlist) == 0:
                                print(f"No valid unitmod for unit {unittouse}")
                                bad = True
                                break
                            unitmodtouse = unitmodlist.pop(0)
                            realunitmod = utils.unitmods[unitmodtouse]

                            # Find the parent secondary effect to test it for paths
                            secondary = utils.unitmodToSecondary(realunitmod,
                                                                 fallback=True)

                            isvalid = _CanUseUnitAndSecondaryCombo(
                                parentobj, unittouse, secondary, realunitmod,
                                spell, chosensummoneffect, actualpowerlvl)
                            if not isvalid:
                                print(
                                    f"... {len(unitmodlist)} unitmods remain..."
                                )
                                continue

                            print(
                                f"Successfully picked secondary {secondary.name} for {unittouse}"
                            )

                            output = f"-- {parentobj.name} applied non-secondary effect unitmod " \
                                     f"{realunitmod.name} to {unittouse.uniqueid}\n\n" + output
                            break

                        if bad:
                            # There was no unitmod, start by finding another chassis
                            unittouse = None
                            continue

                if numtogenerate == 1:
                    unitcode = realunitmod.applytounit(None, unittouse)
                    if parentobj.modulegroup is None:
                        output = unitcode + "\n\n" + output
                    else:
                        parentobj.moduletailingcode += unitcode + "\n"
                    parentobj.lastunitid = realunitmod.lastparentid
                    parentobj.lastunitname = realunitmod.lastunitname
                    if parentobj.usefixedunitid is None or parentobj.usefixedunitid < 0:
                        if "SINGLERANDOMCREATURENAME" in firstspell.details:
                            firstspell.details = firstspell.details.replace(
                                "SINGLERANDOMCREATURENAME",
                                realunitmod.lastunitname, 1)
                        else:
                            firstspell.details += f"The creature for this spell is always a {realunitmod.lastunitname}."

                    generateokay = True
                else:
                    # work out the effective fatigue cost
                    scaleportion = chosensummoneffect.nreff // 1000
                    minnreff = (scaleportion * chosensummoneffect.pathlevel
                                ) + chosensummoneffect.nreff % 1000
                    basecostper = chosensummoneffect.fatiguecost / minnreff

                    # Secondary modification
                    fatiguemult = 1.0
                    if secondary is not None:
                        for attrib, val in secondary.multcommands:
                            if attrib == "fatiguecost":
                                fatiguemult *= val

                    # The chassis/paths split for commanders
                    if chosensummoneffect.chassisvalue is not None and secondary is not None:
                        chosensummoneffect.calcchassisvalues()
                        magiccostmod = chosensummoneffect.magicvaluepercent * basecostper * secondary.magicpathvaluescaling * fatiguemult
                        chassiscostmod = chosensummoneffect.chassisvaluepercent * basecostper * (
                            fatiguemult - 1.0)
                        costper = int(
                            basecostper *
                            (chosensummoneffect.magicvaluepercent +
                             chosensummoneffect.chassisvaluepercent) +
                            magiccostmod + chassiscostmod)
                        costper += secondary.fatiguecostpereffect
                    elif secondary is None:
                        costper = basecostper
                    else:
                        costper = basecostper * fatiguemult
                        costper += secondary.fatiguecostpereffect

                    basepower = chosensummoneffect.power

                    montag.add(unittouse, secondary, costper)
                    generateokay = True

            if realunitmod.lastparentid is not None and numtogenerate == 1:
                output = output.replace("UNITID",
                                        str(realunitmod.lastparentid))
                output = f"-- {parentobj.name} generated with unitid " \
                         f"{realunitmod.lastparentid}\n\n" + output

            if generateokay:
                break
    return (output, montag)
示例#4
0
    def applytounit(self,
                    spell,
                    u,
                    extrashapechain=None,
                    additionals_firstshape=None,
                    spelleffect=None,
                    actualpowerlvl=None,
                    secondaryeffect=None):
        """Apply this effect to the given unit object, returning mod code for the new unit.

                spell can be None. If given, spell.damage is set to the correct unit ID for the new unit.
                extrashapechain should not be set manually as it is used to track wounded shapes and make them all
                join together neatly

                additionals_firstshape should be a dict of {modcommand:value} that is appended to the first shape ONLY.
                So far this is used for things like adding units to montags and giving them a montagweight.
                The modcommand given should INCLUDE A HASH.
                """
        if extrashapechain is None:
            extrashapechain = {}
            self.lastunitAIScore = 0

        u.descr += " " + self.descr
        u.descr = u.descr.replace("CREATURE", u.name)
        u.descr = naming.parsestring(u.descr)
        u.name = self.nameprefix + " " + u.name
        u.name = u.name.strip()
        self.lastunitname = u.name

        out = ""

        # Weapon mod
        wpnmod = utils.weaponmods[self.weaponmod]
        wpnreplacements, wpnoutput = wpnmod.apply(u)
        for wpn in u.weapons:
            if wpn.uniqueid in wpnreplacements:
                wpn.origid = wpnreplacements[wpn.uniqueid]
        out += wpnoutput

        # Event set derived attributes, for things like summoning magicgen creatures or actual event set things
        if self.eventset != "" and self.attributeforrandomunit != "":
            realeventset = utils.eventsets[self.eventset]
            # actualpowerlvl is 0 in all current cases - because commander spells do not have any research level
            # scaling that is not done through secondary effects
            eventsetdata = realeventset.formatdata(spelleffect, spell, 0, None,
                                                   actualpowerlvl)
            if eventsetdata is None:
                return None
            out = eventsetdata + "\n" + out
            setattr(u, self.attributeforrandomunit, realeventset.lastunitid)
            u.additionalmodcmds += f"#{self.attributeforrandomunit} {realeventset.lastunitid}\n"

        if utils.MONSTER_ID >= 9000:
            raise ValueError(
                "New Monster ID is too high for Dominions! Consider changing starting ID or lowering montag sizes."
            )

        out += f"-- Modified unit {u.uniqueid} with unitmod {self.name}\n"
        out += f"#newmonster {utils.MONSTER_ID}\n"
        # Update the spell to summon the new creature
        # needless to say, don't do this if the current monster is a secondshape something caused by running in recursive mode
        # or the spell will be left summoning the last thing in the secondshape chain
        if len(extrashapechain) == 0:
            if spell is not None and spell.effect % 1000 in [
                    1, 21, 37, 38, 43, 50, 89, 93, 119, 137, 54, 130, 62
            ]:
                print(
                    f"unitmod {self.name}: is first pass, fixed spell.damage -> {utils.MONSTER_ID}"
                )
                spell.damage = utils.MONSTER_ID
            else:
                self.lastparentid = utils.MONSTER_ID
        utils.MONSTER_ID += 1
        if u.origid is not None:
            out += f"#copystats {u.origid}\n"
            if u.newunit is None or u.newunit.spr1 is None:
                out += f"#copyspr {u.origid}\n"
            out += "#descr {}{}{}\n".format('"', u.descr, '"')
            out += "#name {}{}{}\n".format('"', u.name, '"')
            out += f"#clearweapons\n"

        # New unit stuff should go here
        out += u.additionalmodcmds + "\n"

        if len(self.addweapons) > 0:
            for wpn in self.addweapons:
                #out += f"#weapon {wpn}\n"
                u.weapons.append(weapon.get(wpn))

        for wpn in u.weapons:
            out += f"#weapon {wpn.origid}\n"

        # Disable transformation
        if hasattr(u, "transformation"):
            if u.transformation != 0:
                out += "#transformation 0\n"

        for param in self.params:
            if hasattr(self, param):
                paramv = getattr(self, param)
                if paramv != 0:
                    if not hasattr(u, param):
                        print(
                            f"Unit {u.name} didn't have param {param} when affected by unitmod {self.name}, assumed 0"
                        )
                        setattr(u, param, 0)
                    newparamval = getattr(u, param) + paramv

                    # do not mess with certain values of morale, they should be done with set commands only
                    if param == "mor":
                        oldval = getattr(u, param)
                        if oldval == 30:
                            newparamval = 30
                        if oldval == 50:
                            newparamval = 50

                    setattr(u, param, newparamval)
                    modcmd = flags_to_mod_cmds.get(param, param)

                    if modcmd not in argless_cmds:
                        out += f"#{modcmd} {newparamval}\n"
                    else:
                        out += f"#{modcmd}\n"

        for param, val in self.setcommands:
            modcmd = flags_to_mod_cmds.get(param, param)
            if modcmd not in argless_cmds:
                if isinstance(val, str):
                    out += "#{} {}{}{}\n".format(modcmd, '"', val, '"')
                else:
                    out += f"#{modcmd} {val}\n"
            else:
                out += f"#{modcmd}\n"

        for param, val in self.multcommands:
            paramv = getattr(u, param)
            newparamval = int(val * paramv)
            setattr(u, param, newparamval)
            modcmd = flags_to_mod_cmds.get(param, param)
            out += f"#{modcmd} {newparamval}\n"

        aiscore = u.calcSummonAIScore()
        self.lastunitAIScore += aiscore
        print(
            f"AIscore for this unit is {aiscore}, total now {self.lastunitAIScore}"
        )

        secondshapeextra = ""
        # Second shape needs to propagate
        for x in [
                "shapechange", "firstshape", "secondshape", "secondtmpshape",
                "forestshape", "plainshape", "foreignshape", "homeshape",
                "domshape", "notdomshape", "springshape", "summershape",
                "autumnshape", "wintershape", "landshape", "watershape"
        ]:
            if hasattr(u, x):
                uid = getattr(u, x)
                oldscore = copy(self.lastunitAIScore)
                if uid not in extrashapechain and uid > 0:
                    extrashapechain[uid] = copy(utils.MONSTER_ID)
                    print(
                        f"UID {uid} is a {x} of {u.name}, applying to that too..."
                    )
                    secondshapeextra += self.applytounit(
                        spell,
                        unitinbasedatafinder.get(uid),
                        extrashapechain,
                        spelleffect=spelleffect,
                        actualpowerlvl=actualpowerlvl,
                        secondaryeffect=secondaryeffect)
                if uid in extrashapechain and uid > 0:
                    # update this creature's secondshape or whatever with the new creature
                    out += f"#{x} {extrashapechain[uid]}\n"
                # only secondshape counts towards AI scores
                if x != "secondshape":
                    self.lastunitAIScore = oldscore

        if additionals_firstshape is not None:
            for modcmd, val in additionals_firstshape.items():
                out += f"{modcmd} {val}\n"

        # Special case: Do nothing should firstshape to the original unit
        # This makes montags built with Do Nothing-modified units quickly transform to their normal version
        # which means double click correctly selects all of them amongst other things

        # ...but not if this is a newunit, as u.id will instead be undefined or whatever we copystats-ed from originally
        if self.name == "Do Nothing" and u.newunit is None:
            out += f"#firstshape {u.id}\n"
        if u.newunit is not None:
            if u.newunit.spr1 is not None:
                utils.spritedependencies.add(u.newunit.spr1)
            if u.newunit.spr2 is not None:
                utils.spritedependencies.add(u.newunit.spr2)

        out += "#end\n\n"

        out += secondshapeextra

        return out
示例#5
0
def generateNewIndepspellsModFile(f, baseucrc):
    indepspellscontent = f"--{baseucrc}\n-- This file (indepspells.dm) is transcluded into generated " \
                         f"mod files when MagicGen clears vanilla generic spells.\n" \
                         f"-- The above is the CRC of the BaseU.csv file used to generate this file " \
                         f"- this is saved as generating this from scratch takes several minutes\n"
    for unitid in range(0, 3500):
        if unitid % 100 == 0:
            _writetoconsole(f"Beginning indepspells for unit {unitid}...\n")
        try:
            unitobj = unitinbasedatafinder.get(unitid)
        except Exception:
            print(f"Error trying to get uid {unitid}")
            print(traceback.format_exc());
            continue
        # leave illwinter-set values intact
        if hasattr(unitobj, "indepspells"):
            if int(getattr(unitobj, "indepspells")) > 0:
                continue
        indeplevel = None

        if hasattr(unitobj, "startdom"):
            if int(getattr(unitobj, "startdom")) > 0:
                indeplevel = 7

        if indeplevel is None:
            totalmagiclevel = 0
            for path in ["F", "A", "W", "E", "S", "D", "N", "B"]:
                if getattr(unitobj, path, "") <= 0:
                    continue
                pathval = int(getattr(unitobj, path, 0))
                if pathval > 0:
                    totalmagiclevel += pathval
                    print(f"Unit {unitid} has {path} level {pathval}")

            randomaverage = 0

            for n in range(1, 5):
                mask = f"mask{n}"

                mask = int(getattr(unitobj, mask, 0))
                randomchance = int(getattr(unitobj, f"rand{n}", 0))
                nbr = max(0, int(getattr(unitobj, f"nbr{n}")))
                if randomchance > 0:
                    randomaverage += nbr * (randomchance / 100)
                    print(
                        f"Unit {unitid} has random {n} giving {nbr} paths with {randomchance}% of success")
                    print(f"\ttotal random path value now {randomaverage}")

            totalmagiclevel += math.floor(randomaverage)

            if totalmagiclevel >= 3:
                indeplevel = 5
            elif totalmagiclevel == 2:
                indeplevel = 4
            elif totalmagiclevel == 1:
                indeplevel = 3

        if indeplevel is not None:
            indepspellscontent += f"#selectmonster {unitid}\n"
            indepspellscontent += f"#indepspells {indeplevel}\n"
            indepspellscontent += f"#end\n"
    with open("indepspells.dm", "w") as indepspells:
        indepspells.write(indepspellscontent)
        f.write(indepspellscontent)