Repository: Arrow-kb/FS25_RealisticLivestock
Branch: main
Commit: b67f612a76c7
Files: 228
Total size: 12.7 MB
Directory structure:
gitextract__64fnynn/
├── .github/
│ └── FUNDING.yml
├── LICENSE
├── README.md
├── animals/
│ └── domesticated/
│ ├── bullRing.i3d
│ ├── bullRing.i3d.shapes
│ ├── bumId.i3d
│ ├── bumId.i3d.shapes
│ ├── bumId_diffuse.dds
│ ├── colorPickerScene.i3d.shapes
│ ├── colourShader.xml
│ ├── cow/
│ │ ├── angus/
│ │ │ ├── cattleAngus.i3d
│ │ │ ├── cattleAngus.i3d.shapes
│ │ │ ├── cattleAngus_diffuse.dds
│ │ │ ├── cattleCalf.i3d
│ │ │ ├── cattleCalf.i3d.shapes
│ │ │ ├── cattleCalfBaby.i3d
│ │ │ └── cattleCalfBaby.i3d.shapes
│ │ ├── animals.xml
│ │ ├── holstein/
│ │ │ ├── cattleCalfBabyHolstein.i3d
│ │ │ ├── cattleCalfBabyHolstein.i3d.shapes
│ │ │ ├── cattleCalfHolstein.i3d
│ │ │ ├── cattleCalfHolstein.i3d.shapes
│ │ │ ├── cattleHolstein.i3d
│ │ │ └── cattleHolstein.i3d.shapes
│ │ └── waterBuffalo/
│ │ ├── waterBuffalo.i3d
│ │ ├── waterBuffalo.i3d.shapes
│ │ ├── waterBuffaloCalf.i3d
│ │ ├── waterBuffaloCalf.i3d.shapes
│ │ ├── waterBuffaloCalfBaby.i3d
│ │ └── waterBuffaloCalfBaby.i3d.shapes
│ ├── earTagScene.i3d
│ ├── earTagScene.i3d.shapes
│ ├── earTags.i3d
│ ├── earTags.i3d.shapes
│ ├── earTagsRight.i3d
│ ├── earTagsRight.i3d.shapes
│ ├── earTags_diffuse.dds
│ ├── font_normal.dds
│ ├── font_normal_white.dds
│ ├── monitor.i3d
│ ├── monitor.i3d.shapes
│ ├── monitor_diffuse.dds
│ ├── offsetUV.xml
│ ├── pigs/
│ │ ├── animals.xml
│ │ ├── pigPiglet.i3d
│ │ ├── pigPiglet.i3d.shapes
│ │ ├── pigPigletBaby.i3d
│ │ ├── pigPigletBaby.i3d.shapes
│ │ ├── pigSow.i3d
│ │ └── pigSow.i3d.shapes
│ ├── sheep/
│ │ ├── animals.xml
│ │ ├── goat.i3d
│ │ ├── goat.i3d.shapes
│ │ ├── goatBaby.i3d
│ │ ├── goatBaby.i3d.shapes
│ │ ├── goatKid.i3d
│ │ ├── goatKid.i3d.shapes
│ │ ├── sheepEwe.i3d
│ │ ├── sheepEwe.i3d.shapes
│ │ ├── sheepLamb.i3d
│ │ ├── sheepLamb.i3d.shapes
│ │ ├── sheepLambBaby.i3d
│ │ └── sheepLambBaby.i3d.shapes
│ ├── sprayedMarker.dds
│ ├── sprayedMarker_cattle.i3d
│ ├── sprayedMarker_cattle.i3d.shapes
│ ├── sprayedMarker_pigs.i3d
│ ├── sprayedMarker_pigs.i3d.shapes
│ ├── sprayedMarker_sheep.i3d
│ ├── sprayedMarker_sheep.i3d.shapes
│ ├── textTemplate.i3d
│ └── textTemplate.i3d.shapes
├── fonts/
│ ├── dejavu_sans/
│ │ ├── dejavu_sans.dds
│ │ ├── dejavu_sansBold.dds
│ │ ├── dejavu_sansBoldItalic.dds
│ │ ├── dejavu_sansBoldItalic_alpha.dds
│ │ ├── dejavu_sansBold_alpha.dds
│ │ ├── dejavu_sansItalic.dds
│ │ ├── dejavu_sansItalic_alpha.dds
│ │ ├── dejavu_sans_alpha.dds
│ │ └── font.xml
│ ├── fonts.xml
│ └── toms_handwritten/
│ ├── font.xml
│ ├── toms_handwritten.dds
│ ├── toms_handwrittenBold.dds
│ ├── toms_handwrittenBoldItalic.dds
│ ├── toms_handwrittenBoldItalic_alpha.dds
│ ├── toms_handwrittenBold_alpha.dds
│ ├── toms_handwrittenItalic.dds
│ ├── toms_handwrittenItalic_alpha.dds
│ └── toms_handwritten_alpha.dds
├── gui/
│ ├── AnimalAIDialog.xml
│ ├── AnimalFilterDialog.xml
│ ├── AnimalInfoDialog.xml
│ ├── AnimalScreen.xml
│ ├── DiseaseDialog.xml
│ ├── EarTagColourPickerDialog.xml
│ ├── FileExplorerDialog.xml
│ ├── NameInputDialog.xml
│ ├── ProfileDialog.xml
│ ├── RealisticLivestockFrame.xml
│ ├── VisualAnimalsDialog.xml
│ ├── fileTypeIcons.dds
│ ├── fileTypeIcons.xml
│ ├── guiProfiles.xml
│ ├── helpicons.dds
│ ├── helpicons.xml
│ ├── icons.dds
│ └── icons.xml
├── icon_RealisticLivestock.dds
├── modDesc.xml
├── objects/
│ ├── dewar/
│ │ ├── alphabet_handwritten.dds
│ │ ├── dewar.dds
│ │ ├── dewar.i3d
│ │ └── dewar.i3d.shapes
│ └── straw/
│ ├── straw.i3d
│ ├── straw.i3d.shapes
│ └── straw.xml
├── src/
│ ├── AIAnimalManager.lua
│ ├── AIStrawUpdater.lua
│ ├── AnimalBirthEvent.lua
│ ├── AnimalDeathEvent.lua
│ ├── AnimalMonitorEvent.lua
│ ├── AnimalNameChangeEvent.lua
│ ├── AnimalPregnancyEvent.lua
│ ├── AnimalUpdateEvent.lua
│ ├── DewarManager.lua
│ ├── Disease.lua
│ ├── DiseaseManager.lua
│ ├── FSCareerMissionInfo.lua
│ ├── I18N.lua
│ ├── RLConsoleCommandManager.lua
│ ├── RLMessage.lua
│ ├── RLSettings.lua
│ ├── RL_BroadcastSettingsEvent.lua
│ ├── RealisticLivestock.lua
│ ├── RealisticLivestock_Animal.lua
│ ├── RealisticLivestock_FSBaseMission.lua
│ ├── animals/
│ │ ├── husbandry/
│ │ │ ├── AnimalSystemStateEvent.lua
│ │ │ ├── RealisticLivestock_AnimalNameSystem.lua
│ │ │ ├── RealisticLivestock_AnimalSystem.lua
│ │ │ ├── RealisticLivestock_HusbandrySystem.lua
│ │ │ ├── cluster/
│ │ │ │ ├── RealisticLivestock_AnimalCluster.lua
│ │ │ │ ├── RealisticLivestock_AnimalClusterHusbandry.lua
│ │ │ │ ├── RealisticLivestock_AnimalClusterSystem.lua
│ │ │ │ └── VisualAnimal.lua
│ │ │ └── placeables/
│ │ │ ├── PlaceableHusbandry.lua
│ │ │ ├── PlaceableHusbandryLiquidManure.lua
│ │ │ ├── PlaceableHusbandryStraw.lua
│ │ │ ├── PlaceableHusbandryWater.lua
│ │ │ ├── RealisticLivestock_PlaceableHusbandryAnimals.lua
│ │ │ ├── RealisticLivestock_PlaceableHusbandryFood.lua
│ │ │ ├── RealisticLivestock_PlaceableHusbandryMilk.lua
│ │ │ └── RealisticLivestock_PlaceableHusbandryPallets.lua
│ │ └── shop/
│ │ ├── AnimalItemNew.lua
│ │ ├── RealisticLivestock_AnimalItemStock.lua
│ │ ├── controllers/
│ │ │ ├── AnimalScreenBase.lua
│ │ │ ├── AnimalScreenDealer.lua
│ │ │ ├── AnimalScreenDealerFarm.lua
│ │ │ ├── AnimalScreenDealerTrailer.lua
│ │ │ ├── AnimalScreenTrailer.lua
│ │ │ └── AnimalScreenTrailerFarm.lua
│ │ └── events/
│ │ ├── AIAnimalBuyEvent.lua
│ │ ├── AIAnimalInseminationEvent.lua
│ │ ├── AIAnimalSellEvent.lua
│ │ ├── AIBulkMessageEvent.lua
│ │ ├── AnimalBuyEvent.lua
│ │ ├── AnimalInseminationEvent.lua
│ │ ├── AnimalInseminationResultEvent.lua
│ │ ├── AnimalMoveEvent.lua
│ │ ├── AnimalSellEvent.lua
│ │ ├── RealisticLivestock_AnimalSellEvent.lua
│ │ └── SemenBuyEvent.lua
│ ├── events/
│ │ ├── DewarManagerStateEvent.lua
│ │ ├── HusbandryMessageStateEvent.lua
│ │ └── ReturnStrawEvent.lua
│ ├── farms/
│ │ ├── FarmManager.lua
│ │ └── RealisticLivestock_FarmStats.lua
│ ├── fillTypes/
│ │ └── RealisticLivestock_FillTypeManager.lua
│ ├── gui/
│ │ ├── AnimalAIDialog.lua
│ │ ├── AnimalFilterDialog.lua
│ │ ├── AnimalInfoDialog.lua
│ │ ├── DiseaseDialog.lua
│ │ ├── EarTagColourPickerDialog.lua
│ │ ├── FileExplorerDialog.lua
│ │ ├── InGameMenuSettingsFrame.lua
│ │ ├── MPLoadingScreen.lua
│ │ ├── NameInputDialog.lua
│ │ ├── ProfileDialog.lua
│ │ ├── RL_InfoDisplayKeyValueBox.lua
│ │ ├── RealisticLivestockFrame.lua
│ │ ├── RealisticLivestock_AnimalScreen.lua
│ │ ├── RealisticLivestock_InGameMenuAnimalsFrame.lua
│ │ ├── VisualAnimalsDialog.lua
│ │ └── elements/
│ │ ├── DoubleOptionSliderElement.lua
│ │ ├── RenderElement.lua
│ │ └── TripleOptionElement.lua
│ ├── handTools/
│ │ ├── HandTool.lua
│ │ ├── HandToolSystem.lua
│ │ ├── RLHandTools.lua
│ │ └── specializations/
│ │ ├── HandToolAIStraw.lua
│ │ └── HandToolHorseBrush.lua
│ ├── objects/
│ │ └── Dewar.lua
│ ├── placeables/
│ │ └── RealisticLivestock_PlaceableSystem.lua
│ ├── player/
│ │ ├── RealisticLivestock_PlayerHUDUpdater.lua
│ │ └── RealisticLivestock_PlayerInputComponent.lua
│ └── vehicles/
│ ├── RealisticLivestock_VehicleSystem.lua
│ └── specializations/
│ ├── RealisticLivestock_LivestockTrailer.lua
│ └── Rideable.lua
├── translations/
│ ├── translation_br.xml
│ ├── translation_cz.xml
│ ├── translation_da.xml
│ ├── translation_de.xml
│ ├── translation_en.xml
│ ├── translation_fi.xml
│ ├── translation_fr.xml
│ ├── translation_it.xml
│ ├── translation_nl.xml
│ ├── translation_pl.xml
│ ├── translation_pt.xml
│ ├── translation_ru.xml
│ ├── translation_tr.xml
│ └── translation_uk.xml
└── xml/
├── animalNames.xml
├── animals.xml
├── diseases.xml
├── fillTypes.xml
└── handTools.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: arrow_kb
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: README.md
================================================
### Important Notice
Due to several factors (mainly due to a long persistent trend of _difficulty_ with GIANTS Software), development and maintenance of all my mods are hereby ceased. I have removed all my mods from the GIANTS ModHub, and no further development will be supported on any platform.
For certain mods, I will be uploading my private development versions to their respective GitHub projects. Anyone who wishes to continue development or maintenance of any mod is allowed to do so, and upload it to the ModHub, with appropriate credit. Any questions related to continued development can be directed to **arrow_kb** on discord.
# FS25_RealisticLivestock
[]()
[Requires FS25_FontLibrary](https://github.com/Arrow-kb/FS25_FontLibrary)
Realistic Livestock mod for FS25.
# Features:
**Male Animals**
- Higher value
- Required for reproduction
- No male models (im not a 3d artist)
**Visual Animals**
- The limit of visual animals per husbandry has been increased from 25 in the vanilla game, up to 200 at maximum which can be changed with a slider
**Individual Animals**
- All animals are now 100% individual and the archaic cluster system has been scrapped, allowing for more variety in gameplay and animals
- Every animal has its own unique identifier based on the UK's cattle identification system
- Every animal has a birthday and a country of origin
- Every animal can be named
- Supported animals have ear tags on their left and right ears, detailing their country of origin, farm ID, animal ID, name and birthday, as well as a partial identifier on their behind
- Supported animals have nose rings
**Enhanced Pregnancy System**
- Animals of different species have varying amounts of potential offspring per each pregnancy (example: cows can have 0-3 calves)
- Offspring is created inside the mother when she is impregnated instead of at birth
- Ability to view the number of expected offspring and their due date
- The reproduction duration is now unique for every animal, not every animal of the same species will take the same length of time to deliver their offspring
**Genetics System**
- New genetics system
- Every animal has several genetic modifiers completely unique to them
- Current genetics are health, fertility, metabolism, productivity and meat quality
- Genetics impact several different areas of the animal, such as pregnancy and sale value
**Weight System**
- New weight system
- Every animal has a weight unique to them, and each day they will gravitate to/from their ideal weight (based on their metabolism) due to how much they eat and drink
- Weight affects sale value and health, and can unhealthy weight can result in death especially for young animals
**Enhanced UI**
- New family tree visible in the animal screen
- Lots of additional information is shown in the animal screen and the info box about each animal
**Enhanced Production**
- Supported animals will have their production based on various factors
- Cows will only produce milk when lactating, and their yield will rise and fall based on their previous pregnancy
- Sheep will only produce wool in warm months
**Death and Aging**
- Animal aging is no longer limited to 5 years
- Every animal type has an expected lifespan, and every animal has a chance to die of old age the older they get, but can live past their expected lifespan
- Animals can die from health, random accidents and bad pregnancies
**Enhanced Animal Dealer**
- The animal dealer has been completely overhauled
- Every supported country in the world now has several unique farms, which each produce up to 3 types of animals
- Each farm will send some of their animals to the animal dealer every day
- Every animal is unique and is no longer a template, and are generated based on the quality of the farm they come from
- Every animal sent to the dealer will eventually disappear and will no longer be able to be bought, but will be replaced with new ones
- Most animals are local to your country, but some will be imported from abroad
- Animals at the dealer have a chance to be pregnant and will all have unique genetic qualities which will impact their price and usefulness
# Screenshots




# In Progress
- Diseases
- Message log tab for husbandries
================================================
FILE: animals/domesticated/bullRing.i3d
================================================
================================================
FILE: animals/domesticated/bumId.i3d
================================================
================================================
FILE: animals/domesticated/colourShader.xml
================================================
================================================
FILE: animals/domesticated/cow/angus/cattleAngus.i3d
================================================
================================================
FILE: animals/domesticated/cow/angus/cattleAngus_diffuse.dds
================================================
[File too large to display: 10.7 MB]
================================================
FILE: animals/domesticated/cow/angus/cattleCalf.i3d
================================================
================================================
FILE: animals/domesticated/cow/angus/cattleCalfBaby.i3d
================================================
================================================
FILE: animals/domesticated/cow/animals.xml
================================================
================================================
FILE: animals/domesticated/cow/holstein/cattleCalfBabyHolstein.i3d
================================================
================================================
FILE: animals/domesticated/cow/holstein/cattleCalfHolstein.i3d
================================================
================================================
FILE: animals/domesticated/cow/holstein/cattleHolstein.i3d
================================================
================================================
FILE: animals/domesticated/cow/waterBuffalo/waterBuffalo.i3d
================================================
================================================
FILE: animals/domesticated/cow/waterBuffalo/waterBuffaloCalf.i3d
================================================
================================================
FILE: animals/domesticated/cow/waterBuffalo/waterBuffaloCalfBaby.i3d
================================================
================================================
FILE: animals/domesticated/earTagScene.i3d
================================================
================================================
FILE: animals/domesticated/earTags.i3d
================================================
================================================
FILE: animals/domesticated/earTagsRight.i3d
================================================
================================================
FILE: animals/domesticated/monitor.i3d
================================================
================================================
FILE: animals/domesticated/offsetUV.xml
================================================
================================================
FILE: animals/domesticated/pigs/animals.xml
================================================
================================================
FILE: animals/domesticated/pigs/pigPiglet.i3d
================================================
================================================
FILE: animals/domesticated/pigs/pigPigletBaby.i3d
================================================
================================================
FILE: animals/domesticated/pigs/pigSow.i3d
================================================
================================================
FILE: animals/domesticated/sheep/animals.xml
================================================
================================================
FILE: animals/domesticated/sheep/goat.i3d
================================================
================================================
FILE: animals/domesticated/sheep/goatBaby.i3d
================================================
================================================
FILE: animals/domesticated/sheep/goatKid.i3d
================================================
================================================
FILE: animals/domesticated/sheep/sheepEwe.i3d
================================================
================================================
FILE: animals/domesticated/sheep/sheepLamb.i3d
================================================
================================================
FILE: animals/domesticated/sheep/sheepLambBaby.i3d
================================================
================================================
FILE: animals/domesticated/sprayedMarker_cattle.i3d
================================================
================================================
FILE: animals/domesticated/sprayedMarker_pigs.i3d
================================================
================================================
FILE: animals/domesticated/sprayedMarker_sheep.i3d
================================================
================================================
FILE: animals/domesticated/textTemplate.i3d
================================================
================================================
FILE: fonts/dejavu_sans/font.xml
================================================
================================================
FILE: fonts/fonts.xml
================================================
================================================
FILE: fonts/toms_handwritten/font.xml
================================================
================================================
FILE: gui/AnimalAIDialog.xml
================================================
================================================
FILE: gui/AnimalFilterDialog.xml
================================================
================================================
FILE: gui/AnimalInfoDialog.xml
================================================
================================================
FILE: gui/AnimalScreen.xml
================================================
================================================
FILE: gui/DiseaseDialog.xml
================================================
================================================
FILE: gui/EarTagColourPickerDialog.xml
================================================
================================================
FILE: gui/FileExplorerDialog.xml
================================================
================================================
FILE: gui/NameInputDialog.xml
================================================
================================================
FILE: gui/ProfileDialog.xml
================================================
================================================
FILE: gui/RealisticLivestockFrame.xml
================================================
================================================
FILE: gui/VisualAnimalsDialog.xml
================================================
================================================
FILE: gui/fileTypeIcons.xml
================================================
fileTypeIcons.png
================================================
FILE: gui/guiProfiles.xml
================================================
================================================
FILE: gui/helpicons.xml
================================================
helpicons.png
================================================
FILE: gui/icons.xml
================================================
icons.png
================================================
FILE: modDesc.xml
================================================
Arrow1.2.1.4Realistic LivestockRealistischer ViehbestandBétail Réalisteicon_RealisticLivestock.ddsFS25_FontLibrary
================================================
FILE: objects/dewar/dewar.i3d
================================================
================================================
FILE: objects/straw/straw.i3d
================================================
================================================
FILE: objects/straw/straw.xml
================================================
objects/straw/straw.i3dAI Straw
================================================
FILE: src/AIAnimalManager.lua
================================================
AIAnimalManager = {}
local AIAnimalManager_mt = Class(AIAnimalManager)
function AIAnimalManager.new(husbandry, isServer)
local self = setmetatable({}, AIAnimalManager_mt)
self.husbandry = husbandry
self.isServer = isServer
self.wage = 0
self.isProfile = false
self.ANIMAL_TYPE_TO_WAGE = {
[AnimalType.COW] = 20,
[AnimalType.SHEEP] = 12.5,
[AnimalType.PIG] = 10,
[AnimalType.HORSE] = 25,
[AnimalType.CHICKEN] = 2
}
self.settings = {
["buy"] = {
["enabled"] = false,
["budget"] = {
["type"] = "fixed",
["fixed"] = 5000,
["percentage"] = 1
},
["maxAnimals"] = 5,
["breed"] = "any",
["diseases"] = false,
["gender"] = "any",
["age"] = {
["min"] = 0,
["max"] = 999
},
["quality"] = {
["min"] = 25,
["max"] = 175
},
["health"] = {
["min"] = 25,
["max"] = 175
},
["fertility"] = {
["min"] = 0,
["max"] = 175
},
["productivity"] = {
["min"] = 25,
["max"] = 175
},
["metabolism"] = {
["min"] = 25,
["max"] = 175
}
},
["sell"] = {
["enabled"] = false,
["maxAnimals"] = 5,
["mark"] = false,
["diseases"] = false,
["gender"] = "any",
["age"] = {
["min"] = 0,
["max"] = 999
},
["quality"] = {
["min"] = 25,
["max"] = 175
},
["health"] = {
["min"] = 25,
["max"] = 175
},
["fertility"] = {
["min"] = 0,
["max"] = 175
},
["productivity"] = {
["min"] = 25,
["max"] = 175
},
["metabolism"] = {
["min"] = 25,
["max"] = 175
}
},
["castrate"] = {
["enabled"] = false,
["mark"] = false,
["diseases"] = false,
["age"] = {
["min"] = 0,
["max"] = 0
},
["quality"] = {
["min"] = 25,
["max"] = 175
},
["health"] = {
["min"] = 25,
["max"] = 175
},
["fertility"] = {
["min"] = 0,
["max"] = 175
},
["productivity"] = {
["min"] = 25,
["max"] = 175
},
["metabolism"] = {
["min"] = 25,
["max"] = 175
}
},
["naming"] = {
["enabled"] = false,
["convention"] = "random",
["previous"] = nil
},
["ai"] = {
["enabled"] = false,
["maxAnimals"] = 5,
["mark"] = false,
["diseases"] = false,
["semen"] = "any",
["age"] = {
["min"] = 0,
["max"] = 999
},
["quality"] = {
["min"] = 25,
["max"] = 175
},
["health"] = {
["min"] = 25,
["max"] = 175
},
["fertility"] = {
["min"] = 0,
["max"] = 175
},
["productivity"] = {
["min"] = 25,
["max"] = 175
},
["metabolism"] = {
["min"] = 25,
["max"] = 175
}
}
}
return self
end
function AIAnimalManager:saveToXMLFile(xmlFile, baseKey)
local key = self.isProfile and baseKey or (baseKey .. ".AIAnimalManager")
local settings = self.settings
if not self.isProfile then xmlFile:setFloat(key .. "#wage", self.wage) end
-- BUY SETTINGS
xmlFile:setBool(key .. ".buy#enabled", settings.buy.enabled)
xmlFile:setString(key .. ".buy.budget#type", settings.buy.budget.type)
xmlFile:setInt(key .. ".buy.budget#fixed", settings.buy.budget.fixed)
xmlFile:setFloat(key .. ".buy.budget#percentage", settings.buy.budget.percentage)
xmlFile:setInt(key .. ".buy#maxAnimals", settings.buy.maxAnimals)
xmlFile:setString(key .. ".buy#breed", settings.buy.breed)
xmlFile:setBool(key .. ".buy#diseases", settings.buy.diseases)
xmlFile:setString(key .. ".buy#gender", settings.buy.gender)
xmlFile:setInt(key .. ".buy.age#min", settings.buy.age.min)
xmlFile:setInt(key .. ".buy.age#max", settings.buy.age.max)
xmlFile:setInt(key .. ".buy.quality#min", settings.buy.quality.min)
xmlFile:setInt(key .. ".buy.quality#max", settings.buy.quality.max)
xmlFile:setInt(key .. ".buy.health#min", settings.buy.health.min)
xmlFile:setInt(key .. ".buy.health#max", settings.buy.health.max)
xmlFile:setInt(key .. ".buy.fertility#min", settings.buy.fertility.min)
xmlFile:setInt(key .. ".buy.fertility#max", settings.buy.fertility.max)
xmlFile:setInt(key .. ".buy.productivity#min", settings.buy.productivity.min)
xmlFile:setInt(key .. ".buy.productivity#max", settings.buy.productivity.max)
xmlFile:setInt(key .. ".buy.metabolism#min", settings.buy.metabolism.min)
xmlFile:setInt(key .. ".buy.metabolism#max", settings.buy.metabolism.max)
-- SELL SETTINGS
xmlFile:setBool(key .. ".sell#enabled", settings.sell.enabled)
xmlFile:setInt(key .. ".sell#maxAnimals", settings.sell.maxAnimals)
xmlFile:setBool(key .. ".sell#mark", settings.sell.mark)
xmlFile:setBool(key .. ".sell#diseases", settings.sell.diseases)
xmlFile:setString(key .. ".sell#gender", settings.sell.gender)
xmlFile:setInt(key .. ".sell.age#min", settings.sell.age.min)
xmlFile:setInt(key .. ".sell.age#max", settings.sell.age.max)
xmlFile:setInt(key .. ".sell.quality#min", settings.sell.quality.min)
xmlFile:setInt(key .. ".sell.quality#max", settings.sell.quality.max)
xmlFile:setInt(key .. ".sell.health#min", settings.sell.health.min)
xmlFile:setInt(key .. ".sell.health#max", settings.sell.health.max)
xmlFile:setInt(key .. ".sell.fertility#min", settings.sell.fertility.min)
xmlFile:setInt(key .. ".sell.fertility#max", settings.sell.fertility.max)
xmlFile:setInt(key .. ".sell.productivity#min", settings.sell.productivity.min)
xmlFile:setInt(key .. ".sell.productivity#max", settings.sell.productivity.max)
xmlFile:setInt(key .. ".sell.metabolism#min", settings.sell.metabolism.min)
xmlFile:setInt(key .. ".sell.metabolism#max", settings.sell.metabolism.max)
-- CASTRATE SETTINGS
xmlFile:setBool(key .. ".castrate#enabled", settings.castrate.enabled)
xmlFile:setBool(key .. ".castrate#mark", settings.castrate.mark)
xmlFile:setBool(key .. ".castrate#diseases", settings.castrate.diseases)
xmlFile:setInt(key .. ".castrate.age#min", settings.castrate.age.min)
xmlFile:setInt(key .. ".castrate.age#max", settings.castrate.age.max)
xmlFile:setInt(key .. ".castrate.quality#min", settings.castrate.quality.min)
xmlFile:setInt(key .. ".castrate.quality#max", settings.castrate.quality.max)
xmlFile:setInt(key .. ".castrate.health#min", settings.castrate.health.min)
xmlFile:setInt(key .. ".castrate.health#max", settings.castrate.health.max)
xmlFile:setInt(key .. ".castrate.fertility#min", settings.castrate.fertility.min)
xmlFile:setInt(key .. ".castrate.fertility#max", settings.castrate.fertility.max)
xmlFile:setInt(key .. ".castrate.productivity#min", settings.castrate.productivity.min)
xmlFile:setInt(key .. ".castrate.productivity#max", settings.castrate.productivity.max)
xmlFile:setInt(key .. ".castrate.metabolism#min", settings.castrate.metabolism.min)
xmlFile:setInt(key .. ".castrate.metabolism#max", settings.castrate.metabolism.max)
-- NAMING SETTINGS
xmlFile:setBool(key .. ".naming#enabled", settings.naming.enabled)
xmlFile:setString(key .. ".naming#convention", settings.naming.convention)
if not self.isProfile and settings.naming.previous ~= nil and settings.naming.convention == "alphabetical" then xmlFile:setString(key .. ".naming#previous", settings.naming.previous) end
-- AI SETTINGS
xmlFile:setBool(key .. ".ai#enabled", settings.ai.enabled)
xmlFile:setInt(key .. ".ai#maxAnimals", settings.ai.maxAnimals)
xmlFile:setBool(key .. ".ai#mark", settings.ai.mark)
xmlFile:setBool(key .. ".ai#diseases", settings.ai.diseases)
xmlFile:setString(key .. ".ai#semen", settings.ai.semen)
xmlFile:setInt(key .. ".ai.age#min", settings.ai.age.min)
xmlFile:setInt(key .. ".ai.age#max", settings.ai.age.max)
xmlFile:setInt(key .. ".ai.quality#min", settings.ai.quality.min)
xmlFile:setInt(key .. ".ai.quality#max", settings.ai.quality.max)
xmlFile:setInt(key .. ".ai.health#min", settings.ai.health.min)
xmlFile:setInt(key .. ".ai.health#max", settings.ai.health.max)
xmlFile:setInt(key .. ".ai.fertility#min", settings.ai.fertility.min)
xmlFile:setInt(key .. ".ai.fertility#max", settings.ai.fertility.max)
xmlFile:setInt(key .. ".ai.productivity#min", settings.ai.productivity.min)
xmlFile:setInt(key .. ".ai.productivity#max", settings.ai.productivity.max)
xmlFile:setInt(key .. ".ai.metabolism#min", settings.ai.metabolism.min)
xmlFile:setInt(key .. ".ai.metabolism#max", settings.ai.metabolism.max)
end
function AIAnimalManager:loadFromXMLFile(xmlFile, baseKey)
local key = self.isProfile and basekey or (baseKey .. ".AIAnimalManager")
local settings = self.settings
self.wage = xmlFile:getFloat(key .. "#wage", 0)
-- BUY SETTINGS
settings.buy.enabled = xmlFile:getBool(key .. ".buy#enabled", settings.buy.enabled)
settings.buy.budget.type = xmlFile:getString(key .. ".buy.budget#type", settings.buy.budget.type)
settings.buy.budget.fixed = xmlFile:getInt(key .. ".buy.budget#fixed", settings.buy.budget.fixed)
settings.buy.budget.percentage = xmlFile:getFloat(key .. ".buy.budget#percentage", settings.buy.budget.percentage)
settings.buy.maxAnimals = xmlFile:getInt(key .. ".buy#maxAnimals", settings.buy.maxAnimals)
settings.buy.breed = xmlFile:getString(key .. ".buy#breed", settings.buy.breed)
settings.buy.diseases = xmlFile:getBool(key .. ".buy#diseases", settings.buy.diseases)
settings.buy.gender = xmlFile:getString(key .. ".buy#gender", settings.buy.gender)
settings.buy.age.min = xmlFile:getInt(key .. ".buy.age#min", settings.buy.age.min)
settings.buy.age.max = xmlFile:getInt(key .. ".buy.age#max", settings.buy.age.max)
settings.buy.quality.min = xmlFile:getInt(key .. ".buy.quality#min", settings.buy.quality.min)
settings.buy.quality.max = xmlFile:getInt(key .. ".buy.quality#max", settings.buy.quality.max)
settings.buy.health.min = xmlFile:getInt(key .. ".buy.health#min", settings.buy.health.min)
settings.buy.health.max = xmlFile:getInt(key .. ".buy.health#max", settings.buy.health.max)
settings.buy.fertility.min = xmlFile:getInt(key .. ".buy.fertility#min", settings.buy.fertility.min)
settings.buy.fertility.max = xmlFile:getInt(key .. ".buy.fertility#max", settings.buy.fertility.max)
settings.buy.productivity.min = xmlFile:getInt(key .. ".buy.productivity#min", settings.buy.productivity.min)
settings.buy.productivity.max = xmlFile:getInt(key .. ".buy.productivity#max", settings.buy.productivity.max)
settings.buy.metabolism.min = xmlFile:getInt(key .. ".buy.metabolism#min", settings.buy.metabolism.min)
settings.buy.metabolism.max = xmlFile:getInt(key .. ".buy.metabolism#max", settings.buy.metabolism.max)
-- SELL SETTINGS
settings.sell.enabled = xmlFile:getBool(key .. ".sell#enabled", settings.sell.enabled)
settings.sell.maxAnimals = xmlFile:getInt(key .. ".sell#maxAnimals", settings.sell.maxAnimals)
settings.sell.mark = xmlFile:getBool(key .. ".sell#mark", settings.sell.mark)
settings.sell.diseases = xmlFile:getBool(key .. ".sell#diseases", settings.sell.diseases)
settings.sell.gender = xmlFile:getString(key .. ".sell#gender", settings.sell.gender)
settings.sell.age.min = xmlFile:getInt(key .. ".sell.age#min", settings.sell.age.min)
settings.sell.age.max = xmlFile:getInt(key .. ".sell.age#max", settings.sell.age.max)
settings.sell.quality.min = xmlFile:getInt(key .. ".sell.quality#min", settings.sell.quality.min)
settings.sell.quality.max = xmlFile:getInt(key .. ".sell.quality#max", settings.sell.quality.max)
settings.sell.health.min = xmlFile:getInt(key .. ".sell.health#min", settings.sell.health.min)
settings.sell.health.max = xmlFile:getInt(key .. ".sell.health#max", settings.sell.health.max)
settings.sell.fertility.min = xmlFile:getInt(key .. ".sell.fertility#min", settings.sell.fertility.min)
settings.sell.fertility.max = xmlFile:getInt(key .. ".sell.fertility#max", settings.sell.fertility.max)
settings.sell.productivity.min = xmlFile:getInt(key .. ".sell.productivity#min", settings.sell.productivity.min)
settings.sell.productivity.max = xmlFile:getInt(key .. ".sell.productivity#max", settings.sell.productivity.max)
settings.sell.metabolism.min = xmlFile:getInt(key .. ".sell.metabolism#min", settings.sell.metabolism.min)
settings.sell.metabolism.max = xmlFile:getInt(key .. ".sell.metabolism#max", settings.sell.metabolism.max)
-- CASTRATE SETTINGS
settings.castrate.enabled = xmlFile:getBool(key .. ".castrate#enabled", settings.castrate.enabled)
settings.castrate.mark = xmlFile:getBool(key .. ".castrate#mark", settings.castrate.mark)
settings.castrate.diseases = xmlFile:getBool(key .. ".castrate#diseases", settings.castrate.diseases)
settings.castrate.age.min = xmlFile:getInt(key .. ".castrate.age#min", settings.castrate.age.min)
settings.castrate.age.max = xmlFile:getInt(key .. ".castrate.age#max", settings.castrate.age.max)
settings.castrate.quality.min = xmlFile:getInt(key .. ".castrate.quality#min", settings.castrate.quality.min)
settings.castrate.quality.max = xmlFile:getInt(key .. ".castrate.quality#max", settings.castrate.quality.max)
settings.castrate.health.min = xmlFile:getInt(key .. ".castrate.health#min", settings.castrate.health.min)
settings.castrate.health.max = xmlFile:getInt(key .. ".castrate.health#max", settings.castrate.health.max)
settings.castrate.fertility.min = xmlFile:getInt(key .. ".castrate.fertility#min", settings.castrate.fertility.min)
settings.castrate.fertility.max = xmlFile:getInt(key .. ".castrate.fertility#max", settings.castrate.fertility.max)
settings.castrate.productivity.min = xmlFile:getInt(key .. ".castrate.productivity#min", settings.castrate.productivity.min)
settings.castrate.productivity.max = xmlFile:getInt(key .. ".castrate.productivity#max", settings.castrate.productivity.max)
settings.castrate.metabolism.min = xmlFile:getInt(key .. ".castrate.metabolism#min", settings.castrate.metabolism.min)
settings.castrate.metabolism.max = xmlFile:getInt(key .. ".castrate.metabolism#max", settings.castrate.metabolism.max)
-- NAMING SETTINGS
settings.naming.enabled = xmlFile:getBool(key .. ".naming#enabled", settings.naming.enabled)
settings.naming.convention = xmlFile:getString(key .. ".naming#convention", settings.naming.convention)
settings.naming.previous = xmlFile:getString(key .. ".naming#previous", settings.naming.previous)
-- AI SETTINGS
settings.ai.enabled = xmlFile:getBool(key .. ".ai#enabled", settings.ai.enabled)
settings.ai.maxAnimals = xmlFile:getInt(key .. ".ai#maxAnimals", settings.ai.maxAnimals)
settings.ai.mark = xmlFile:getBool(key .. ".ai#mark", settings.ai.mark)
settings.ai.diseases = xmlFile:getBool(key .. ".ai#diseases", settings.ai.diseases)
settings.ai.semen = xmlFile:getString(key .. ".ai#semen", settings.ai.semen)
settings.ai.age.min = xmlFile:getInt(key .. ".ai.age#min", settings.ai.age.min)
settings.ai.age.max = xmlFile:getInt(key .. ".ai.age#max", settings.ai.age.max)
settings.ai.quality.min = xmlFile:getInt(key .. ".ai.quality#min", settings.ai.quality.min)
settings.ai.quality.max = xmlFile:getInt(key .. ".ai.quality#max", settings.ai.quality.max)
settings.ai.health.min = xmlFile:getInt(key .. ".ai.health#min", settings.ai.health.min)
settings.ai.health.max = xmlFile:getInt(key .. ".ai.health#max", settings.ai.health.max)
settings.ai.fertility.min = xmlFile:getInt(key .. ".ai.fertility#min", settings.ai.fertility.min)
settings.ai.fertility.max = xmlFile:getInt(key .. ".ai.fertility#max", settings.ai.fertility.max)
settings.ai.productivity.min = xmlFile:getInt(key .. ".ai.productivity#min", settings.ai.productivity.min)
settings.ai.productivity.max = xmlFile:getInt(key .. ".ai.productivity#max", settings.ai.productivity.max)
settings.ai.metabolism.min = xmlFile:getInt(key .. ".ai.metabolism#min", settings.ai.metabolism.min)
settings.ai.metabolism.max = xmlFile:getInt(key .. ".ai.metabolism#max", settings.ai.metabolism.max)
end
function AIAnimalManager:getSettings(type)
return type == nil and self.settings or self.settings[type]
end
function AIAnimalManager:setSettings(settings, type)
if type == nil then
self.settings = settings
else
self.settings[type] = settings
end
end
function AIAnimalManager:onDayChanged()
if not self.isServer then return end
local buy, sell, castrate, naming, ai = self.settings.buy, self.settings.sell, self.settings.castrate, self.settings.naming, self.settings.ai
self.wage = 0
if not buy.enabled and not sell.enabled and not castrate.enabled and not naming.enabled and not ai.enabled then return end
local farmId = self.husbandry:getOwnerFarmId()
local farm = g_farmManager:getFarmById(farmId)
local animalSystem = g_currentMission.animalSystem
local animalTypeIndex = self.husbandry:getAnimalTypeIndex()
local animalTypeToWage = self.ANIMAL_TYPE_TO_WAGE[animalTypeIndex] or 5
local messages = {}
-- ####################### SELL #######################
if sell.enabled and sell.maxAnimals > 0 then
local soldAnimals = 0
local animals = self.husbandry:getClusters()
local shortlist = {}
local qualityMin, qualityMax = sell.quality.min / 100, sell.quality.max / 100
local fertilityMin, fertilityMax = sell.fertility.min / 100, sell.fertility.max / 100
local healthMin, healthMax = sell.health.min / 100, sell.health.max / 100
local metabolismMin, metabolismMax = sell.metabolism.min / 100, sell.metabolism.max / 100
local productivityMin, productivityMax = sell.productivity.min / 100, sell.productivity.max / 100
for _, animal in pairs(animals) do
if animal:getMarked("AI_MANAGER_SELL") then animal:setMarked("AI_MANAGER_SELL", false) end
if sell.gender ~= "any" and animal.gender ~= sell.gender then continue end
if animal.age < sell.age.min or animal.age > sell.age.max then continue end
if sell.diseases and (animal.diseases == nil or #animal.diseases == 0) then continue end
if animal.genetics.metabolism < metabolismMin or animal.genetics.metabolism > metabolismMax then continue end
if animal.genetics.quality < qualityMin or animal.genetics.quality > qualityMax then continue end
if animal.genetics.fertility < fertilityMin or animal.genetics.fertility > fertilityMax then continue end
if animal.genetics.health < healthMin or animal.genetics.health > healthMax then continue end
if animal.genetics.productivity ~= nil and (animal.genetics.productivity < productivityMin or animal.genetics.productivity > productivityMax) then continue end
local price = animal:getSellPrice() + animalSystem:getAnimalTransportFee(animal.subTypeIndex, animal.age)
table.insert(shortlist, { ["animal"] = animal, ["price"] = price })
end
local soldAnimals, amountGained = {}, 0
table.sort(shortlist, function(a, b) return a.price > b.price end)
local mark = sell.mark
for _, item in ipairs(shortlist) do
if #soldAnimals >= sell.maxAnimals then break end
amountGained = amountGained + item.price
table.insert(soldAnimals, item.animal)
if mark then item.animal:setMarked("AI_MANAGER_SELL", true) end
end
local mark = sell.mark
self.wage = self.wage + animalTypeToWage * #soldAnimals * (mark and 0.35 or 1) + animalTypeToWage * math.min(#shortlist, #soldAnimals * 5) * 0.15 * (mark and 0.35 or 1)
if #soldAnimals > 0 and not mark then
local errorCode = AIAnimalSellEvent.validate(self.husbandry, #soldAnimals, amountGained, farmId)
if errorCode == nil then
g_server:broadcastEvent(AIAnimalSellEvent.new(self.husbandry, soldAnimals, amountGained), true)
if #soldAnimals == 1 then
self.husbandry:addRLMessage("AI_MANAGER_SOLD_SINGLE", nil, { g_i18n:formatMoney(amountGained, 2, true, true) })
table.insert(messages, { ["id"] = "AI_MANAGER_SOLD_SINGLE", ["args"] = { g_i18n:formatMoney(amountGained, 2, true, true) } })
else
self.husbandry:addRLMessage("AI_MANAGER_SOLD_MULTIPLE", nil, { #soldAnimals, g_i18n:formatMoney(amountGained, 2, true, true) })
table.insert(messages, { ["id"] = "AI_MANAGER_SOLD_MULTIPLE", ["args"] = { #soldAnimals, g_i18n:formatMoney(amountGained, 2, true, true) } })
end
end
elseif #soldAnimals > 0 then
if #soldAnimals == 1 then
self.husbandry:addRLMessage("AI_MANAGER_MARK_SELL_SINGLE")
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_SELL_SINGLE" })
else
self.husbandry:addRLMessage("AI_MANAGER_MARK_SELL_MULTIPLE", nil, { #soldAnimals })
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_SELL_MULTIPLE", ["args"] = { #soldAnimals }})
end
end
end
-- ####################### BUY #######################
if buy.enabled and buy.maxAnimals > 0 then
local budget = buy.budget.fixed
if buy.budget.type == "percentage" then budget = math.floor(farm:getBalance() * (buy.budget.percentage / 100)) end
budget = math.clamp(budget, 0, farm:getBalance())
if budget > 0 then
local animals = animalSystem:getSaleAnimalsByTypeIndex(animalTypeIndex)
local shortlist = {}
local qualityMin, qualityMax = buy.quality.min / 100, buy.quality.max / 100
local fertilityMin, fertilityMax = buy.fertility.min / 100, buy.fertility.max / 100
local healthMin, healthMax = buy.health.min / 100, buy.health.max / 100
local metabolismMin, metabolismMax = buy.metabolism.min / 100, buy.metabolism.max / 100
local productivityMin, productivityMax = buy.productivity.min / 100, buy.productivity.max / 100
for _, animal in pairs(animals) do
if animal.reserved then continue end
if buy.gender ~= "any" and animal.gender ~= buy.gender then continue end
if buy.breed ~= "any" and animal.breed ~= buy.breed then continue end
if animal.age < buy.age.min or animal.age > buy.age.max then continue end
if not buy.diseases and #animal.diseases > 0 then continue end
if animal.genetics.metabolism < metabolismMin or animal.genetics.metabolism > metabolismMax then continue end
if animal.genetics.quality < qualityMin or animal.genetics.quality > qualityMax then continue end
if animal.genetics.fertility < fertilityMin or animal.genetics.fertility > fertilityMax then continue end
if animal.genetics.health < healthMin or animal.genetics.health > healthMax then continue end
if animal.genetics.productivity ~= nil and (animal.genetics.productivity < productivityMin or animal.genetics.productivity > productivityMax) then continue end
local price = animal:getSellPrice() * 1.075 + animalSystem:getAnimalTransportFee(animal.subTypeIndex, animal.age)
if price > budget then continue end
table.insert(shortlist, { ["animal"] = animal, ["price"] = price })
end
local boughtAnimals, amountSpent = {}, 0
table.sort(shortlist, function(a, b) return a.price < b.price end)
for _, item in ipairs(shortlist) do
if item.price > budget or #boughtAnimals >= buy.maxAnimals then break end
amountSpent = amountSpent + item.price
budget = budget - item.price
item.animal.reserved = true
table.insert(boughtAnimals, item.animal)
end
if #boughtAnimals > 0 then
local errorCode = AIAnimalBuyEvent.validate(self.husbandry, #boughtAnimals, amountSpent, farmId)
if errorCode == nil then
g_server:broadcastEvent(AIAnimalBuyEvent.new(self.husbandry, boughtAnimals, amountSpent), true)
if #boughtAnimals == 1 then
self.husbandry:addRLMessage("AI_MANAGER_BOUGHT_SINGLE", nil, { g_i18n:formatMoney(amountSpent, 2, true, true) })
table.insert(messages, { ["id"] = "AI_MANAGER_BOUGHT_SINGLE", ["args"] = { g_i18n:formatMoney(amountSpent, 2, true, true) } })
else
self.husbandry:addRLMessage("AI_MANAGER_BOUGHT_MULTIPLE", nil, { #boughtAnimals, g_i18n:formatMoney(amountSpent, 2, true, true) })
table.insert(messages, { ["id"] = "AI_MANAGER_BOUGHT_MULTIPLE", ["args"] = { #boughtAnimals, g_i18n:formatMoney(amountSpent, 2, true, true) } })
end
end
end
self.wage = self.wage + animalTypeToWage * #boughtAnimals + animalTypeToWage * math.min(#shortlist, #boughtAnimals * 5) * 0.15
end
end
-- ####################### CASTRATE #######################
if castrate.enabled and animalTypeIndex ~= AnimalType.CHICKEN then
local animals = self.husbandry:getClusters()
local numCastrated = 0
local qualityMin, qualityMax = castrate.quality.min / 100, castrate.quality.max / 100
local fertilityMin, fertilityMax = castrate.fertility.min / 100, castrate.fertility.max / 100
local healthMin, healthMax = castrate.health.min / 100, castrate.health.max / 100
local metabolismMin, metabolismMax = castrate.metabolism.min / 100, castrate.metabolism.max / 100
local productivityMin, productivityMax = castrate.productivity.min / 100, castrate.productivity.max / 100
for _, animal in pairs(animals) do
if animal:getMarked("AI_MANAGER_CASTRATE") then animal:setMarked("AI_MANAGER_CASTRATE", false) end
if animal.gender == "female" or animal.isCastrated or animal.genetics.fertility == 0 then continue end
if animal.age < castrate.age.min or animal.age > castrate.age.max then continue end
if castrate.diseases and (animal.diseases == nil or #animal.diseases == 0) then continue end
if animal.genetics.metabolism < metabolismMin or animal.genetics.metabolism > metabolismMax then continue end
if animal.genetics.quality < qualityMin or animal.genetics.quality > qualityMax then continue end
if animal.genetics.fertility < fertilityMin or animal.genetics.fertility > fertilityMax then continue end
if animal.genetics.health < healthMin or animal.genetics.health > healthMax then continue end
if animal.genetics.productivity ~= nil and (animal.genetics.productivity < productivityMin or animal.genetics.productivity > productivityMax) then continue end
if not castrate.mark then
animal.isCastrated = true
animal.genetics.fertility = 0
else
animal:setMarked("AI_MANAGER_CASTRATE", true)
end
self.wage = self.wage + animalTypeToWage * 0.5 * (castrate.mark and 0.35 or 1)
numCastrated = numCastrated + 1
end
if castrate.mark then
if numCastrated == 1 then
self.husbandry:addRLMessage("AI_MANAGER_MARK_CASTRATE_SINGLE")
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_CASTRATE_SINGLE" })
elseif numCastrated > 0 then
self.husbandry:addRLMessage("AI_MANAGER_MARK_CASTRATE_MULTIPLE", nil, { numCastrated })
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_CASTRATE_MULTIPLE", ["args"] = { numCastrated } })
end
else
if numCastrated == 1 then
self.husbandry:addRLMessage("AI_MANAGER_MARK_CASTRATE_SINGLE")
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_CASTRATE_SINGLE" })
elseif numCastrated > 0 then
self.husbandry:addRLMessage("AI_MANAGER_MARK_CASTRATE_MULTIPLE", nil, { numCastrated })
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_CASTRATE_MULTIPLE", ["args"] = { numCastrated } })
end
end
end
-- ####################### NAMING #######################
if naming.enabled then
local animals = self.husbandry:getClusters()
local animalNameSystem = g_currentMission.animalNameSystem
local femaleNames = animalNameSystem:getNamesAlphabetical("female")
local maleNames = animalNameSystem:getNamesAlphabetical("male")
local numNamed = 0
for _, animal in pairs(animals) do
if animal.name ~= nil and animal.name ~= "" then continue end
if naming.convention == "random" then
animal.name = animalNameSystem:getRandomName(animal.gender)
self.wage = self.wage + animalTypeToWage * 0.15
numNamed = numNamed + 1
else
local names = animal.gender == "female" and femaleNames or maleNames
for i, name in ipairs(names) do
if naming.previous == nil or (name ~= naming.previous and name >= naming.previous) or i == #names then
if i == #names and naming.previous == name then
animal.name = names[1] .. ""
naming.previous = names[1] .. ""
else
animal.name = name .. ""
naming.previous = name .. ""
end
self.wage = self.wage + animalTypeToWage * 0.15
numNamed = numNamed + 1
break
end
end
end
end
if numNamed == 1 then
self.husbandry:addRLMessage("AI_MANAGER_NAMED_SINGLE")
table.insert(messages, { ["id"] = "AI_MANAGER_NAMED_SINGLE" })
elseif numNamed > 0 then
self.husbandry:addRLMessage("AI_MANAGER_NAMED_MULTIPLE", nil, { numNamed })
table.insert(messages, { ["id"] = "AI_MANAGER_NAMED_MULTIPLE", ["args"] = { numNamed } })
end
end
-- ####################### AI #######################
if ai.enabled and ai.maxAnimals > 0 then
local farmDewars = g_dewarManager:getDewarsByFarm(farmId)
local dewars
if ai.semen ~= "any" and farmDewars ~= nil then
for _, dewar in pairs(farmDewars[animalTypeIndex] or {}) do
if dewar:getUniqueId() == ai.semen then
dewars = { dewar }
break
end
end
elseif farmDewars ~= nil then
dewars = farmDewars[animalTypeIndex]
end
local inseminatedAnimals = 0
local animals = self.husbandry:getClusters()
local shortlist = {}
local dewarToStrawDelta = {}
local qualityMin, qualityMax = ai.quality.min / 100, ai.quality.max / 100
local fertilityMin, fertilityMax = ai.fertility.min / 100, ai.fertility.max / 100
local healthMin, healthMax = ai.health.min / 100, ai.health.max / 100
local metabolismMin, metabolismMax = ai.metabolism.min / 100, sell.metabolism.max / 100
local productivityMin, productivityMax = ai.productivity.min / 100, ai.productivity.max / 100
for _, animal in pairs(animals) do
if animal:getMarked("AI_MANAGER_INSEMINATE") then animal:setMarked("AI_MANAGER_INSEMINATE", false) end
if dewars == nil or #dewars == 0 then continue end
if animal.age < ai.age.min or animal.age > ai.age.max then continue end
if ai.diseases and (animal.diseases == nil or #animal.diseases == 0) then continue end
if animal.genetics.metabolism < metabolismMin or animal.genetics.metabolism > metabolismMax then continue end
if animal.genetics.quality < qualityMin or animal.genetics.quality > qualityMax then continue end
if animal.genetics.fertility < fertilityMin or animal.genetics.fertility > fertilityMax then continue end
if animal.genetics.health < healthMin or animal.genetics.health > healthMax then continue end
if animal.genetics.productivity ~= nil and (animal.genetics.productivity < productivityMin or animal.genetics.productivity > productivityMax) then continue end
local canBeInseminated = false
local usedDewar
for _, dewar in pairs(dewars) do
if dewar.animal == nil or dewar.straws <= 0 then continue end
if dewarToStrawDelta[dewar] ~= nil and dewar.straws - dewarToStrawDelta[dewar] <= 0 then continue end
canBeInseminated = animal:getCanBeInseminatedByAnimal(dewar.animal)
if canBeInseminated then
usedDewar = dewar
if dewarToStrawDelta[dewar] == nil then dewarToStrawDelta[dewar] = 0 end
dewarToStrawDelta[dewar] = dewarToStrawDelta[dewar] + 1
break
end
end
if not canBeInseminated then continue end
local genetics = animal.genetics.metabolism + animal.genetics.quality + animal.genetics.fertility + animal.genetics.health + (animal.genetics.productivity or 0)
table.insert(shortlist, { ["animal"] = animal, ["genetics"] = genetics, ["dewar"] = usedDewar })
end
local inseminatedAnimals = {}
table.sort(shortlist, function(a, b) return a.genetics > b.genetics end)
local mark = ai.mark
for _, item in ipairs(shortlist) do
if #inseminatedAnimals >= ai.maxAnimals then break end
table.insert(inseminatedAnimals, { ["animal"] = item.animal, ["dewar"] = item.dewar:getUniqueId() })
if mark then item.animal:setMarked("AI_MANAGER_INSEMINATE", true) end
end
local mark = ai.mark
self.wage = self.wage + animalTypeToWage * #inseminatedAnimals * (mark and 0.45 or 1.2) + animalTypeToWage * math.min(#shortlist, #inseminatedAnimals * 5) * 0.2 * (mark and 0.35 or 1)
if #inseminatedAnimals > 0 and not mark then
g_server:broadcastEvent(AIAnimalInseminationEvent.new(self.husbandry, inseminatedAnimals), true)
if #inseminatedAnimals == 1 then
self.husbandry:addRLMessage("AI_MANAGER_INSEMINATED_SINGLE")
table.insert(messages, { ["id"] = "AI_MANAGER_INSEMINATED_SINGLE" })
else
self.husbandry:addRLMessage("AI_MANAGER_INSEMINATED_MULTIPLE", nil, { #inseminatedAnimals })
table.insert(messages, { ["id"] = "AI_MANAGER_INSEMINATED_MULTIPLE", ["args"] = { #inseminatedAnimals } })
end
elseif #inseminatedAnimals > 0 then
if #inseminatedAnimals == 1 then
self.husbandry:addRLMessage("AI_MANAGER_MARK_INSEMINATED_SINGLE")
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_INSEMINATED_SINGLE" })
else
self.husbandry:addRLMessage("AI_MANAGER_MARK_INSEMINATED_MULTIPLE", nil, { #inseminatedAnimals })
table.insert(messages, { ["id"] = "AI_MANAGER_MARK_INSEMINATED_MULTIPLE", ["args"] = { #inseminatedAnimals } })
end
end
end
if #messages > 0 and g_server.netIsRunning then g_server:broadcastEvent(AIBulkMessageEvent.new(self.husbandry, messages)) end
end
function AIAnimalManager:createProfile()
local profile = AIAnimalManager.new(self.isServer)
profile.settings = table.clone(self.settings, 10)
profile.isProfile = true
return profile
end
function AIAnimalManager:copyProfile(profile)
self.settings = table.clone(profile.settings, 10)
end
function InGameMenuStatisticsFrame:updateViewHandTools()
local sortByColumnHandTools = self.sortByColumnHandTools
local sortOrderHandTools = self.sortOrderHandTools
for v156, v157 in pairs(self.sortIconsHandTools) do
local v158 = v156 == sortByColumnHandTools
local v159 = v157[InGameMenuStatisticsFrame.SORT_ORDER_DESC]
local v160
if v158 then
v160 = sortOrderHandTools == InGameMenuStatisticsFrame.SORT_ORDER_DESC
else
v160 = v158
end
v159:setVisible(v160)
local v161 = v157[InGameMenuStatisticsFrame.SORT_ORDER_ASC]
if v158 then v158 = sortOrderHandTools == InGameMenuStatisticsFrame.SORT_ORDER_ASC end
v161:setVisible(v158)
end
table.sort(self.handTools, function(a, b)
local aValue = a.columns[sortByColumnHandTools].value
local bValue = b.columns[sortByColumnHandTools].value
print("a")
DebugUtil.printTableRecursively(a.columns[sortByColumnHandTools], " - ", 0, 1)
print("b")
DebugUtil.printTableRecursively(b.columns[sortByColumnHandTools], " - ", 0, 1)
print("------------")
if aValue == bValue then
aValue = a.columns[InGameMenuStatisticsFrame.COLUMN_NAME].value
bValue = b.columns[InGameMenuStatisticsFrame.COLUMN_NAME].value
if aValue == bValue then
aValue = a.columns[InGameMenuStatisticsFrame.COLUMN_HOLDER].value
bValue = b.columns[InGameMenuStatisticsFrame.COLUMN_HOLDER].value
end
end
if sortOrderHandTools == InGameMenuStatisticsFrame.SORT_ORDER_DESC then return bValue < aValue end
return aValue < bValue
end)
self.handToolsList:reloadData()
end
================================================
FILE: src/AIStrawUpdater.lua
================================================
AIStrawUpdater = {}
local AIStrawUpdater_mt = Class(AIStrawUpdater)
function AIStrawUpdater.new()
local self = setmetatable({}, AIStrawUpdater_mt)
return self
end
function AIStrawUpdater:update(dT)
if self.straw == nil then return end
self.straw:updateStraw(dT)
end
function AIStrawUpdater:setStraw(straw)
self.straw = straw
end
g_aiStrawUpdater = AIStrawUpdater.new()
================================================
FILE: src/AnimalBirthEvent.lua
================================================
AnimalBirthEvent = {}
local AnimalBirthEvent_mt = Class(AnimalBirthEvent, Event)
InitEventClass(AnimalBirthEvent, "AnimalBirthEvent")
function AnimalBirthEvent.emptyNew()
local self = Event.new(AnimalBirthEvent_mt)
return self
end
function AnimalBirthEvent.new(object, animal, children, parentDied)
local self = AnimalBirthEvent.emptyNew()
self.object = object
self.animal = animal
self.children = children or {}
self.parentDied = parentDied or false
return self
end
function AnimalBirthEvent:readStream(streamId, connection)
local hasObject = streamReadBool(streamId)
self.object = hasObject and NetworkUtil.readNodeObject(streamId) or nil
self.animal = Animal.readStreamIdentifiers(streamId, connection)
local numChildren = streamReadUInt8(streamId)
self.children = {}
for i = 1, numChildren do
local child = Animal.new()
child:readStream(streamId, connection)
table.insert(self.children, child)
end
self.parentDied = streamReadBool(streamId)
self:run(connection)
end
function AnimalBirthEvent:writeStream(streamId, connection)
streamWriteBool(streamId, self.object ~= nil)
if self.object ~= nil then NetworkUtil.writeNodeObject(streamId, self.object) end
self.animal:writeStreamIdentifiers(streamId, connection)
streamWriteUInt8(streamId, #self.children)
for _, child in pairs(self.children) do child:writeStream(streamId, connection) end
streamWriteBool(streamId, self.parentDied)
end
function AnimalBirthEvent:run(connection)
local identifiers = self.animal
if self.object == nil then
local animals = g_currentMission.animalSystem.animals[identifiers.animalTypeIndex]
for _, child in pairs(self.children) do table.insert(animals, child) end
for i, animal in pairs(animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal.isParent = true
animal.monthsSinceLastBirth = 0
animal.pregnancy = nil
animal.impregnatedBy = nil
animal.isPregnant = false
animal.reproduction = 0
if animal.animalTypeIndex == AnimalType.COW or animal.subType == "GOAT" then animal.isLactating = true end
if self.parentDied then table.remove(animals, i) end
break
end
end
else
local clusterSystem = self.object:getClusterSystem()
for _, child in pairs(self.children) do clusterSystem:addCluster(child) end
for _, animal in pairs(clusterSystem.animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal.isParent = true
animal.monthsSinceLastBirth = 0
animal.pregnancy = nil
animal.impregnatedBy = nil
animal.isPregnant = false
animal.reproduction = 0
if animal.animalTypeIndex == AnimalType.COW or animal.subType == "GOAT" then animal.isLactating = true end
break
end
end
if self.parentDied then clusterSystem:removeCluster(identifiers.farmId .. " " .. identifiers.uniqueId .. " " .. (identifiers.country or identifiers.birthday.country)) end
end
end
================================================
FILE: src/AnimalDeathEvent.lua
================================================
AnimalDeathEvent = {}
local AnimalDeathEvent_mt = Class(AnimalDeathEvent, Event)
InitEventClass(AnimalDeathEvent, "AnimalDeathEvent")
function AnimalDeathEvent.emptyNew()
local self = Event.new(AnimalDeathEvent_mt)
return self
end
function AnimalDeathEvent.new(object, animal)
local self = AnimalDeathEvent.emptyNew()
self.object = object
self.animal = animal
return self
end
function AnimalDeathEvent:readStream(streamId, connection)
local hasObject = streamReadBool(streamId)
self.object = hasObject and NetworkUtil.readNodeObject(streamId) or nil
self.animal = Animal.readStreamIdentifiers(streamId, connection)
self:run(connection)
end
function AnimalDeathEvent:writeStream(streamId, connection)
streamWriteBool(streamId, self.object ~= nil)
if self.object ~= nil then NetworkUtil.writeNodeObject(streamId, self.object) end
self.animal:writeStreamIdentifiers(streamId, connection)
end
function AnimalDeathEvent:run(connection)
local identifiers = self.animal
if self.object == nil then
local animals = g_currentMission.animalSystem.animals[identifiers.animalTypeIndex]
for i, animal in pairs(animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
table.remove(animals, i)
return
end
end
else
self.object:getClusterSystem():removeCluster(identifiers.farmId .. " " .. identifiers.uniqueId .. " " .. (identifiers.country or identifiers.birthday.country))
end
end
================================================
FILE: src/AnimalMonitorEvent.lua
================================================
AnimalMonitorEvent = {}
local AnimalMonitorEvent_mt = Class(AnimalMonitorEvent, Event)
InitEventClass(AnimalMonitorEvent, "AnimalMonitorEvent")
function AnimalMonitorEvent.emptyNew()
local self = Event.new(AnimalMonitorEvent_mt)
return self
end
function AnimalMonitorEvent.new(object, animal, active, removed)
local self = AnimalMonitorEvent.emptyNew()
self.object = object
self.animal = animal
self.active = active
self.removed = removed
return self
end
function AnimalMonitorEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
self.animal = Animal.readStreamIdentifiers(streamId, connection)
self.active = streamReadBool(streamId)
self.removed = streamReadBool(streamId)
self:run(connection)
end
function AnimalMonitorEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
self.animal:writeStreamIdentifiers(streamId, connection)
streamWriteBool(streamId, self.active)
streamWriteBool(streamId, self.removed)
end
function AnimalMonitorEvent:run(connection)
local identifiers = self.animal
local clusterSystem = self.object:getClusterSystem()
for _, animal in pairs(clusterSystem.animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal.monitor.active = self.active
animal.monitor.removed = self.removed
return
end
end
end
function AnimalMonitorEvent.sendEvent(object, animal, active, removed)
if g_server ~= nil then
g_server:broadcastEvent(AnimalMonitorEvent.new(object, animal, active, removed))
else
g_client:getServerConnection():sendEvent(AnimalMonitorEvent.new(object, animal, active, removed))
end
end
================================================
FILE: src/AnimalNameChangeEvent.lua
================================================
AnimalNameChangeEvent = {}
local AnimalNameChangeEvent_mt = Class(AnimalNameChangeEvent, Event)
InitEventClass(AnimalNameChangeEvent, "AnimalNameChangeEvent")
function AnimalNameChangeEvent.emptyNew()
local self = Event.new(AnimalNameChangeEvent_mt)
return self
end
function AnimalNameChangeEvent.new(object, animal, name)
local self = AnimalNameChangeEvent.emptyNew()
self.object = object
self.animal = animal
self.name = name
return self
end
function AnimalNameChangeEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
self.animal = Animal.readStreamIdentifiers(streamId, connection)
local hasName = streamReadBool(streamId)
if hasName then self.name = streamReadString(streamId) end
self:run(connection)
end
function AnimalNameChangeEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
self.animal:writeStreamIdentifiers(streamId, connection)
streamWriteBool(streamId, self.name ~= nil and self.name ~= "")
if self.name ~= nil and self.name ~= "" then streamWriteString(streamId, self.name) end
end
function AnimalNameChangeEvent:run(connection)
local identifiers = self.animal
local clusterSystem = self.object:getClusterSystem()
for _, animal in pairs(clusterSystem.animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal.name = self.name
return
end
end
end
function AnimalNameChangeEvent.sendEvent(object, animal, name)
if g_server ~= nil then
g_server:broadcastEvent(AnimalNameChangeEvent.new(object, animal, name))
else
g_client:getServerConnection():sendEvent(AnimalNameChangeEvent.new(object, animal, name))
end
end
================================================
FILE: src/AnimalPregnancyEvent.lua
================================================
AnimalPregnancyEvent = {}
local AnimalPregnancyEvent_mt = Class(AnimalPregnancyEvent, Event)
InitEventClass(AnimalPregnancyEvent, "AnimalPregnancyEvent")
function AnimalPregnancyEvent.emptyNew()
local self = Event.new(AnimalPregnancyEvent_mt)
return self
end
function AnimalPregnancyEvent.new(object, animal)
local self = AnimalPregnancyEvent.emptyNew()
self.object = object
self.animal = animal
return self
end
function AnimalPregnancyEvent:readStream(streamId, connection)
local hasObject = streamReadBool(streamId)
self.object = hasObject and NetworkUtil.readNodeObject(streamId) or nil
self.animal = Animal.readStreamIdentifiers(streamId, connection)
local pregnancy = { ["expected"] = {}, ["pregnancies"] = {} }
local impregnatedBy = {}
pregnancy.duration = streamReadUInt8(streamId)
pregnancy.expected.day = streamReadUInt8(streamId)
pregnancy.expected.month = streamReadUInt8(streamId)
pregnancy.expected.year = streamReadUInt8(streamId)
local numChildren = streamReadUInt8(streamId)
for i = 1, numChildren do
local child = Animal.new()
child:readStreamUnborn(streamId, connection)
if child ~= nil then table.insert(pregnancy.pregnancies, child) end
end
impregnatedBy.uniqueId = streamReadString(streamId)
impregnatedBy.metabolism = streamReadFloat32(streamId)
impregnatedBy.health = streamReadFloat32(streamId)
impregnatedBy.fertility = streamReadFloat32(streamId)
impregnatedBy.quality = streamReadFloat32(streamId)
impregnatedBy.productivity = streamReadFloat32(streamId)
self.pregnancy = pregnancy
self.impregnatedBy = impregnatedBy
self:run(connection)
end
function AnimalPregnancyEvent:writeStream(streamId, connection)
streamWriteBool(streamId, self.object ~= nil)
if self.object ~= nil then
NetworkUtil.writeNodeObject(streamId, self.object)
end
self.animal:writeStreamIdentifiers(streamId, connection)
local pregnancy = self.animal.pregnancy
local impregnatedBy = self.animal.impregnatedBy
streamWriteUInt8(streamId, pregnancy.duration)
streamWriteUInt8(streamId, pregnancy.expected.day)
streamWriteUInt8(streamId, pregnancy.expected.month)
streamWriteUInt8(streamId, pregnancy.expected.year)
streamWriteUInt8(streamId, #pregnancy.pregnancies)
for _, child in pairs(pregnancy.pregnancies) do
child:writeStreamUnborn(streamId, connection)
end
streamWriteString(streamId, impregnatedBy.uniqueId)
streamWriteFloat32(streamId, impregnatedBy.metabolism)
streamWriteFloat32(streamId, impregnatedBy.health)
streamWriteFloat32(streamId, impregnatedBy.fertility)
streamWriteFloat32(streamId, impregnatedBy.quality)
streamWriteFloat32(streamId, impregnatedBy.productivity)
end
function AnimalPregnancyEvent:run(connection)
local identifiers = self.animal
local animals
if self.object == nil then
animals = g_currentMission.animalSystem.animals[identifiers.animalTypeIndex]
else
animals = self.object:getClusterSystem().animals
end
for _, animal in pairs(animals) do
if animal.uniqueId == identifiers.unique and animal.farmId == identifiers.farmId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal.isPregnant = true
animal.pregnancy = self.pregnancy
animal.impregnatedBy = self.impregnatedBy
animal.reproduction = 0
animal:changeReproduction(animal:getReproductionDelta())
return
end
end
end
================================================
FILE: src/AnimalUpdateEvent.lua
================================================
AnimalUpdateEvent = {}
local AnimalUpdateEvent_mt = Class(AnimalUpdateEvent, Event)
InitEventClass(AnimalUpdateEvent, "AnimalUpdateEvent")
function AnimalUpdateEvent.emptyNew()
local self = Event.new(AnimalUpdateEvent_mt)
return self
end
function AnimalUpdateEvent.new(object, animal, trait, value)
local self = AnimalUpdateEvent.emptyNew()
self.object = object
self.animal = animal
self.trait = trait
self.value = value
return self
end
function AnimalUpdateEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
self.animal = Animal.readStreamIdentifiers(streamId, connection)
self.trait = streamReadString(streamId)
local valueType = streamReadString(streamId)
if valueType == "number" then
self.value = streamReadFloat32(streamId)
elseif valueType == "string" then
self.value = streamReadString(streamId)
else
self.value = streamReadBool(streamId)
end
self:run(connection)
end
function AnimalUpdateEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
self.animal:writeStreamIdentifiers(streamId, connection)
streamWriteString(streamId, self.trait)
local valueType = type(self.value)
streamWriteString(streamId, valueType)
if valueType == "number" then
streamWriteFloat32(streamId, self.value)
elseif valueType == "string" then
streamWriteString(streamId, self.value)
else
streamWriteBool(streamId, self.value)
end
end
function AnimalUpdateEvent:run(connection)
local clusterSystem = self.object:getClusterSystem()
local identifiers = self.animal
for _, animal in pairs(clusterSystem.animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) and animal.animalTypeIndex == identifiers.animalTypeIndex then
animal[self.trait] = self.value
return
end
end
end
================================================
FILE: src/DewarManager.lua
================================================
DewarManager = {}
local DewarManager_mt = Class(DewarManager)
local modDirectory = g_currentModDirectory
function DewarManager.new()
local self = setmetatable({}, DewarManager_mt)
self.farms = {}
return self
end
function DewarManager:addDewar(farmId, dewar)
if self.farms[farmId] == nil then self.farms[farmId] = {} end
local farm = self.farms[farmId]
local typeIndex = dewar.animal.typeIndex
if farm[typeIndex] == nil then farm[typeIndex] = {} end
table.insert(farm[typeIndex], dewar)
end
function DewarManager:removeDewar(farmId, dewar)
local typeIndex = dewar.animal.typeIndex
if self.farms[farmId] == nil or self.farms[farmId][typeIndex] == nil then return end
local id = dewar:getUniqueId()
for i, object in pairs(self.farms[farmId][typeIndex]) do
if object:getUniqueId() == id then
table.remove(self.farms[farmId][typeIndex], i)
return
end
end
end
function DewarManager:getDewarsByFarm(farmId)
return self.farms[farmId]
end
function DewarManager:readStream(streamId, connection)
local numFarms = streamReadUInt8(streamId)
self.farms = {}
for farmIndex = 1, numFarms do
local farmId = streamReadUInt8(streamId)
local numAnimalTypes = streamReadUInt8(streamId)
local farm = {}
for animalIndex = 1, numAnimalTypes do
local animalTypeIndex = streamReadUInt8(streamId)
local numDewars = streamReadUInt8(streamId)
local dewars = {}
for dewarIndex = 1, numDewars do
local dewar = Dewar.new(g_currentMission:getIsServer(), g_currentMission:getIsClient())
dewar:createNode(modDirectory .. "objects/dewar/dewar.i3d")
dewar:readStream(streamId, connection)
dewar:register()
table.insert(dewars, dewar)
end
farm[animalTypeIndex] = dewars
end
self.farms[farmId] = farm
end
end
function DewarManager:writeStream(streamId, connection)
local numFarms = 0
for farmId, animalTypes in pairs(self.farms) do numFarms = numFarms + 1 end
streamWriteUInt8(streamId, numFarms)
for farmId, animalTypes in pairs(self.farms) do
local numAnimalTypes = 0
for animalTypeIndex, dewars in pairs(animalTypes) do numAnimalTypes = numAnimalTypes + 1 end
streamWriteUInt8(streamId, farmId)
streamWriteUInt8(streamId, numAnimalTypes)
for animalTypeIndex, dewars in pairs(animalTypes) do
streamWriteUInt8(streamId, animalTypeIndex)
streamWriteUInt8(streamId, #dewars)
for _, dewar in pairs(dewars) do dewar:writeStream(streamId, connection) end
end
end
end
g_dewarManager = DewarManager.new()
================================================
FILE: src/Disease.lua
================================================
Disease = {}
local disease_mt = Class(Disease)
function Disease.new(type, isCarrier, genes)
local self = setmetatable({}, disease_mt)
self.type = type
self.cured = false
self.beingTreated = false
self.treatmentDuration = 0
self.immunity = 0
self.time = -1
self.isCarrier = isCarrier or false
self.genes = genes or 0
self.recovery = 0
return self
end
function Disease:loadFromXMLFile(xmlFile, key)
self.cured = xmlFile:getBool(key .. "#cured", false)
self.beingTreated = xmlFile:getBool(key .. "#beingTreated", false)
self.treatmentDuration = xmlFile:getInt(key .. "#treatmentDuration", 0)
self.immunity = xmlFile:getInt(key .. "#immunity", 0)
self.time = xmlFile:getInt(key .. "#time", 0)
self.isCarrier = xmlFile:getBool(key .. "#isCarrier", false)
self.genes = xmlFile:getInt(key .. "#genes", 0)
self.recovery = xmlFile:getInt(key .. "#recovery", 0)
end
function Disease:saveToXMLFile(xmlFile, key)
xmlFile:setString(key .. "#title", self.type.title)
xmlFile:setBool(key .. "#cured", self.cured)
xmlFile:setBool(key .. "#beingTreated", self.beingTreated)
xmlFile:setInt(key .. "#treatmentDuration", self.treatmentDuration)
xmlFile:setInt(key .. "#immunity", self.immunity)
xmlFile:setInt(key .. "#time", self.time)
xmlFile:setBool(key .. "#isCarrier", self.isCarrier)
xmlFile:setInt(key .. "#genes", self.genes)
xmlFile:setInt(key .. "#recovery", self.recovery)
end
function Disease:writeStream(streamId, connection)
streamWriteString(streamId, self.type.title)
streamWriteBool(streamId, self.cured)
streamWriteBool(streamId, self.beingTreated)
streamWriteUInt8(streamId, self.treatmentDuration)
streamWriteUInt8(streamId, self.immunity)
streamWriteUInt16(streamId, self.time)
streamWriteBool(streamId, self.isCarrier)
streamWriteUInt8(streamId, self.genes)
streamWriteUInt8(streamId, self.recovery)
end
function Disease:readStream(streamId, connection)
self.cured = streamReadBool(streamId)
self.beingTreated = streamReadBool(streamId)
self.treatmentDuration = streamReadUInt8(streamId)
self.immunity = streamReadUInt8(streamId)
self.time = streamReadUInt16(streamId)
self.isCarrier = streamReadBool(streamId)
self.genes = streamReadUInt8(streamId)
self.recovery = streamReadUInt8(streamId)
end
function Disease:onPeriodChanged(animal, deathEnabled)
if not g_diseaseManager.diseasesEnabled then return false, 0 end
self.time = self.time + 1
local treatmentCost = 0
if self.cured then
self.immunity = self.immunity - 1
if self.immunity <= 0 then
animal:removeDisease(self.type.title)
return false, 0
end
elseif self.beingTreated and self.type.treatment ~= nil then
self.treatmentDuration = math.max(self.treatmentDuration - 1, 0)
treatmentCost = self.type.treatment.cost
if self.treatmentDuration <= 0 then
self.cured = true
self.beingTreated = false
self.immunity = self.type.immunity - 0
end
end
if not self.cured and self.type.recovery ~= nil then
self.recovery = self.recovery + 1
if self.recovery >= self.type.recovery and math.random() >= 0.25 then
self.cured = true
self.immunity = self.type.immunity - 0
self.beingTreated = false
end
end
if not self.isCarrier and deathEnabled then
local fatality = self.type.fatality
local fatalityChance = 0
for i = 1, #fatality do
if self.time <= fatality[i].time or i == #fatality then
fatalityChance = fatality[i].value
break
end
end
if math.random() < fatalityChance then
animal:die(self.type.key)
return true, treatmentCost
end
end
return false, treatmentCost
end
function Disease:affectReproduction(child, otherParent)
if not g_diseaseManager.diseasesEnabled then return end
local genetic = self.type.genetic
if genetic == nil or (not genetic.recessive and not genetic.dominant) then return end
local pDisease = otherParent ~= nil and otherParent:getDisease(self.type.title) or nil
local parents = {
self.genes,
pDisease ~= nil and pDisease.genes or 0
}
local numAffectedGenes = 0
for _, genes in pairs(parents) do
if genes == 2 then
numAffectedGenes = numAffectedGenes + 1
elseif genes == 1 then
if math.random() <= 0.5 then numAffectedGenes = numAffectedGenes + 1 end
end
end
if numAffectedGenes == 2 then
child:addDisease(self.type, false, 2)
elseif numAffectedGenes == 1 then
if genetic.recessive then
child:addDisease(self.type, true, 1)
elseif genetic.dominant then
child:addDisease(self.type, false, 1)
end
end
end
function Disease:modifyValue(value)
return value * self.type.value
end
function Disease:modifyOutput(type, value)
if self.cured or not g_diseaseManager.diseasesEnabled then return value end
if self.isCarrier and self.type.carrier ~= nil and self.type.carrier.output ~= nil then return value * (self.type.carrier.output[type] or 1) end
return value * (self.type.output[type] or 1)
end
function Disease:showInfo(box)
local time
local years = math.floor(self.time / 12)
local months = self.time - years * 12
if years == 0 then
time = string.format("%d %s", months, months == 1 and g_i18n:getText("rl_ui_month") or g_i18n:getText("rl_ui_months"))
elseif months == 0 then
time = string.format("%d %s", years, years == 1 and g_i18n:getText("rl_ui_year") or g_i18n:getText("rl_ui_years"))
else
time = string.format("%d %s, %d %s", years, years == 1 and g_i18n:getText("rl_ui_year") or g_i18n:getText("rl_ui_years"), months, months == 1 and g_i18n:getText("rl_ui_month") or g_i18n:getText("rl_ui_months"))
end
box:addLine(string.format("%s (%s)", self.type.name, time), self:getStatus())
end
function Disease:getStatus()
local status
local years = math.floor(self.time / 12)
local months = self.time - years * 12
if self.beingTreated then
status = g_i18n:getText("rl_ui_beingTreated")
elseif self.cured then
local immunityYears = math.floor(self.immunity / 12)
local immunityMonths = self.immunity - immunityYears * 12
local immuneTime
if years == 0 then
immuneTime = string.format("%d %s", months, months == 1 and g_i18n:getText("rl_ui_month") or g_i18n:getText("rl_ui_months"))
elseif months == 0 then
immuneTime = string.format("%d %s", years, years == 1 and g_i18n:getText("rl_ui_year") or g_i18n:getText("rl_ui_years"))
else
immuneTime = string.format("%d %s, %d %s", years, years == 1 and g_i18n:getText("rl_ui_year") or g_i18n:getText("rl_ui_years"), months, months == 1 and g_i18n:getText("rl_ui_month") or g_i18n:getText("rl_ui_months"))
end
status = string.format("%s (%s)", g_i18n:getText("rl_ui_immune"), immuneTime)
elseif self.isCarrier then
status = g_i18n:getText("rl_ui_carrier")
else
status = g_i18n:getText("rl_ui_notTreated")
end
return status
end
================================================
FILE: src/DiseaseManager.lua
================================================
DiseaseManager = {}
local modDirectory = g_currentModDirectory
local diseaseManager_mt = Class(DiseaseManager)
function DiseaseManager.new()
local self = setmetatable({}, diseaseManager_mt)
self.diseases = {}
self.diseasesEnabled = true
self.diseasesChance = 1
self:loadDiseases()
return self
end
function DiseaseManager:loadDiseases()
local xmlFile = XMLFile.loadIfExists("diseases", modDirectory .. "xml/diseases.xml")
if xmlFile == nil then return end
xmlFile:iterate("diseases.disease", function(_, key)
local title = xmlFile:getString(key .. "#title")
local translationKey = "rl_disease_" .. title
local name = g_i18n:getText(translationKey)
local animals = {}
local animalTitles = string.split(xmlFile:getString(key .. "#animals"), " ")
for _, animalTitle in pairs(animalTitles) do animals[AnimalType[animalTitle]] = true end
local valueModifier = xmlFile:getFloat(key .. "#value", 1.0)
local transmission = xmlFile:getFloat(key .. "#transmission", 0)
local immunity = xmlFile:getInt(key .. "#immunity", 12)
local prerequisites = {}
xmlFile:iterate(key .. ".prerequisites.prerequisite", function(_, prerequisiteKey)
local valueType = xmlFile:getString(prerequisiteKey .. "#valueType", "Int")
table.insert(prerequisites, {
["path"] = string.split(xmlFile:getString(prerequisiteKey .. "#path"), "."),
["value"] = XMLFile["get" .. valueType](xmlFile, prerequisiteKey .. "#value")
})
end)
local probability = {}
xmlFile:iterate(key .. ".probability.key", function(_, probabilityKey)
table.insert(probability, {
["age"] = xmlFile:getInt(probabilityKey .. "#age"),
["value"] = xmlFile:getFloat(probabilityKey .. "#value")
})
end)
local fatality = {}
xmlFile:iterate(key .. ".fatality.key", function(_, fatalityKey)
table.insert(fatality, {
["time"] = xmlFile:getInt(fatalityKey .. "#time"),
["value"] = xmlFile:getFloat(fatalityKey .. "#value")
})
end)
local output = {}
xmlFile:iterate(key .. ".output.fillType", function(_, outputKey)
output[xmlFile:getString(outputKey .. "#type")] = xmlFile:getFloat(outputKey .. "#modifier")
end)
local treatment = {
["cost"] = xmlFile:getFloat(key .. ".treatment#cost"),
["duration"] = xmlFile:getInt(key .. ".treatment#duration")
}
if treatment.cost == nil or treatment.duration == nil then treatment = nil end
local recovery = xmlFile:getFloat(key .. "#recovery")
local disease = {
["title"] = title,
["key"] = translationKey,
["name"] = name,
["animals"] = animals,
["value"] = valueModifier,
["transmission"] = transmission,
["immunity"] = immunity,
["prerequisites"] = prerequisites,
["probability"] = probability,
["fatality"] = fatality,
["output"] = output,
["treatment"] = treatment,
["recovery"] = recovery
}
if xmlFile:hasProperty(key .. ".carrier") then
local carrier = {}
if xmlFile:hasProperty(key .. ".carrier.output") then
local carrierOutput = {}
xmlFile:iterate(key .. ".output.fillType", function(_, outputKey)
carrierOutput[xmlFile:getString(outputKey .. "#type")] = xmlFile:getFloat(outputKey .. "#modifier")
end)
carrier.output = carrierOutput
end
disease.carrier = carrier
end
if xmlFile:hasProperty(key .. ".genetic") then
disease.genetic = {
["recessive"] = xmlFile:getBool(key .. ".genetic#recessive", false),
["dominant"] = xmlFile:getBool(key .. ".genetic#dominant", false),
["saleChance"] = xmlFile:getFloat(key .. ".genetic#saleChance", 0)
}
end
table.insert(self.diseases, disease)
end)
xmlFile:delete()
end
function DiseaseManager:getDiseaseByTitle(title)
for _, disease in pairs(self.diseases) do
if disease.title == title then return disease end
end
return nil
end
function DiseaseManager:onDayChanged(animal)
if not self.diseasesEnabled then return end
for _, disease in pairs(self.diseases) do
if not disease.animals[animal.animalTypeIndex] then continue end
local eligible = true
for _, existingDisease in pairs(animal.diseases) do
if existingDisease.type.title == disease.title then
eligible = false
break
end
end
if not eligible then continue end
for _, prerequisite in pairs(disease.prerequisites) do
local currentValue = animal
for _, path in pairs(prerequisite.path) do
currentValue = currentValue[path]
end
if currentValue ~= prerequisite.value then
eligible = false
break
end
end
if not eligible then continue end
local probability = 0
for i = 1, #disease.probability do
if animal.age <= disease.probability[i].age or i == #disease.probability then
probability = disease.probability[i].value
break
end
end
if math.random() >= probability * self.diseasesChance then continue end
animal:addDisease(disease)
end
end
function DiseaseManager:setGeneticDiseasesForSaleAnimal(animal)
for _, disease in pairs(self.diseases) do
if not disease.animals[animal.animalTypeIndex] or disease.genetic == nil or disease.probability[1].value ~= 0 or #disease.probability > 1 then continue end
local eligible = true
for _, existingDisease in pairs(animal.diseases) do
if existingDisease.type.title == disease.title then
eligible = false
break
end
end
if not eligible then continue end
if math.random() < disease.genetic.saleChance then
local numGenes = 1
if math.random() <= 0.25 then numGenes = 2 end
animal:addDisease(disease, disease.genetic.recessive and numGenes == 1, numGenes)
end
end
end
function DiseaseManager:calculateTransmission(animals)
if not self.diseasesEnabled then return end
local diseases = {}
local hasDiseases = false
for _, animal in pairs(animals) do
for _, disease in pairs(animal.diseases) do
local type = disease.type
if type.transmission == nil or type.transmission <= 0 then continue end
if diseases[type.title] == nil then
diseases[type.title] = { ["type"] = type, ["amount"] = 0 }
hasDiseases = true
end
diseases[type.title].amount = diseases[type.title].amount + 1
end
end
if not hasDiseases then return end
for _, animal in pairs(animals) do
for title, disease in pairs(diseases) do
local eligible = true
for _, existingDisease in pairs(animal.diseases) do
if existingDisease.type.title == title then
eligible = false
break
end
end
if not eligible then continue end
for _, prerequisite in pairs(disease.type.prerequisites) do
local currentValue = animal
for _, path in pairs(prerequisite.path) do
currentValue = currentValue[path]
end
if currentValue ~= prerequisite.value then
eligible = false
break
end
end
if not eligible then continue end
if math.random() <= disease.type.transmission * (disease.amount / #animals) then
animal:addDisease(disease.type)
end
end
end
end
function DiseaseManager.onSettingChanged(name, state)
if g_diseaseManager ~= nil then g_diseaseManager[name] = state end
end
================================================
FILE: src/FSCareerMissionInfo.lua
================================================
RL_FSCareerMissionInfo = {}
function RL_FSCareerMissionInfo:saveToXMLFile()
if self.xmlFile ~= nil and g_currentMission ~= nil and g_currentMission.animalSystem ~= nil then
g_currentMission.animalSystem:saveToXMLFile(self.savegameDirectory .. "/animalSystem.xml")
RLSettings.saveToXMLFile()
end
end
FSCareerMissionInfo.saveToXMLFile = Utils.appendedFunction(FSCareerMissionInfo.saveToXMLFile, RL_FSCareerMissionInfo.saveToXMLFile)
================================================
FILE: src/I18N.lua
================================================
RL_I18N = {}
local modName = g_currentModName
local isGithubVersion = true
function RL_I18N:getText(superFunc, text, modEnv)
if (text == "rl_ui_monitorSubscriptions" or text == "finance_monitorSubscriptions" or text == "rl_ui_herdsmanWages" or text == "finance_herdsmanWages" or text == "rl_ui_semenPurchase" or text == "finance_semenPurchase") and modEnv == nil then
return superFunc(self, text, modName)
end
if isGithubVersion and string.contains(text, "rl_") then
local env = self.modEnvironments[modName]
if env == nil then return superFunc(self, text, modEnv) end
if env.texts[text .. "_github"] ~= nil then return env.texts[text .. "_github"] end
end
return superFunc(self, text, modEnv)
end
I18N.getText = Utils.overwrittenFunction(I18N.getText, RL_I18N.getText)
================================================
FILE: src/RLConsoleCommandManager.lua
================================================
RLConsoleCommandManager = {}
local rlConsoleCommandManager_mt = Class(RLConsoleCommandManager)
function RLConsoleCommandManager.new()
local self = setmetatable({}, rlConsoleCommandManager_mt)
self.husbandrySystem = g_currentMission.husbandrySystem
self.animalSystem = g_currentMission.animalSystem
self.animal = nil
self.placeable = nil
if g_currentMission:getIsServer() and not g_currentMission.missionDynamicInfo.isMultiplayer then
addConsoleCommand("rlSetTargetAnimal", "Set the target animal for future console commands", "setAnimal", self, "[type] [farmId] [uniqueId]")
addConsoleCommand("rlSetAnimalGenetics", "Set the genetics of the targeted animal", "setGenetics", self, "[geneticType] [value]")
addConsoleCommand("rlSetAnimalInput", "Set the input of the targeted animal", "setInput", self, "[inputType] [value]")
addConsoleCommand("rlSetAnimalOutput", "Set the output of the targeted animal", "setOutput", self, "[outputType] [value]")
end
return self
end
function RLConsoleCommandManager:setAnimal(animalType, farmId, uniqueId)
self.animal = nil
self.placeable = nil
if animalType == nil or type(animalType) ~= "string" then
print("rlSetTargetAnimal: no animal type given, accepted types:")
for name, index in pairs(AnimalType) do print("|--- " .. name) end
return
end
if farmId == nil then return "rlSetTargetAnimal: no farmId given" end
if uniqueId == nil then return "rlSetTargetAnimal: no uniqueId given" end
local animalTypeIndex = AnimalType[animalType:upper()]
for _, placeable in pairs(self.husbandrySystem.placeables) do
if placeable:getAnimalTypeIndex() ~= animalTypeIndex then continue end
local animals = placeable:getClusters()
for _, animal in pairs(animals) do
if animal.farmId == farmId and animal.uniqueId == uniqueId then
self.animal = animal
self.placeable = placeable
return "rlSetTargetAnimal: animal set successfully"
end
end
end
for _, trailer in pairs(self.husbandrySystem.livestockTrailers) do
local trailerType = trailer:getCurrentAnimalType()
if trailerType == nil or trailerType.typeIndex ~= animalTypeIndex then continue end
local animals = trailer:getClusters()
for _, animal in pairs(animals) do
if animal.farmId == farmId and animal.uniqueId == uniqueId then
self.animal = animal
return "rlSetTargetAnimal: animal set successfully"
end
end
end
return "rlSetTargetAnimal: animal not found"
end
function RLConsoleCommandManager:setGenetics(geneticType, value)
if self.animal == nil then return "rlSetAnimalGenetics: no targeted animal" end
if geneticType == nil or type(geneticType) ~= "string" or self.animal.genetics[geneticType] == nil then
print("rlSetAnimalGenetics: invalid genetic type given, accepted types:")
for key, _ in pairs(self.animal.genetics) do print("|--- " .. key) end
return
end
if value == nil then return "rlSetAnimalGenetics: no value given" end
value = tonumber(value)
if value == nil then return "rlSetAnimalGenetics: invalid value given" end
if value < 0.25 or value > 1.75 then return "rlSetAnimalGenetics: invalid value given, must be in range 0.25 - 1.75" end
self.animal.genetics[geneticType] = value
return "rlSetAnimalGenetics: animal genetics set successfully"
end
function RLConsoleCommandManager:setInput(inputType, value)
if self.animal == nil then return "rlSetAnimalInput: no targeted animal" end
if inputType == nil or type(inputType) ~= "string" or self.animal.input[inputType] == nil then
print("rlSetAnimalInput: invalid input type given, accepted types:")
for key, _ in pairs(self.animal.input) do print("|--- " .. key) end
return
end
if value == nil then return "rlSetAnimalInput: no value given" end
value = tonumber(value)
if value == nil then return "rlSetAnimalInput: invalid value given" end
if value < 0 then return "rlSetAnimalInput: invalid value given, must be higher than or equal to 0" end
self.animal.input[inputType] = value
if self.placeable ~= nil then self.placeable:updateInputAndOutput(self.placeable:getClusters()) end
return "rlSetAnimalInput: animal input set successfully"
end
function RLConsoleCommandManager:setOutput(outputType, value)
if self.animal == nil then return "rlSetAnimalOutput: no targeted animal" end
if outputType == nil or type(outputType) ~= "string" or self.animal.output[outputType] == nil then
print("rlSetAnimalOutput: invalid output type given, accepted types:")
for key, _ in pairs(self.animal.output) do print("|--- " .. key) end
return
end
if value == nil then return "rlSetAnimalOutput: no value given" end
value = tonumber(value)
if value == nil then return "rlSetAnimalOutput: invalid value given" end
if value < 0 then return "rlSetAnimalOutput: invalid value given, must be higher than or equal to 0" end
self.animal.output[outputType] = value
if self.placeable ~= nil then self.placeable:updateInputAndOutput(self.placeable:getClusters()) end
return "rlSetAnimalOutput: animal output set successfully"
end
================================================
FILE: src/RLMessage.lua
================================================
RLMessage = {
["PREGNANCY_SINGLE"] = {
["text"] = "pregnancySingle",
["title"] = "pregnancy",
["importance"] = 2
},
["PREGNANCY_MULTIPLE"] = {
["text"] = "pregnancyMultiple",
["title"] = "pregnancy",
["importance"] = 2
},
["PREGNANCY_SOLD"] = {
["text"] = "pregnancySold",
["title"] = "pregnancy",
["importance"] = 3
},
["PREGNANCY_DIED"] = {
["text"] = "pregnancyDied",
["title"] = "pregnancy",
["importance"] = 3
},
["DEATH"] = {
["text"] = "death",
["title"] = "death",
["importance"] = 1
},
["DISEASE_CONTRACTED"] = {
["text"] = "diseaseContracted",
["title"] = "disease",
["importance"] = 2
},
["DISEASE_CURED"] = {
["text"] = "diseaseCured",
["title"] = "disease",
["importance"] = 2
},
["DISEASE_TREATMENT_START"] = {
["text"] = "diseaseTreatment_start",
["title"] = "disease",
["importance"] = 3
},
["DISEASE_TREATMENT_RESUME"] = {
["text"] = "diseaseTreatment_resume",
["title"] = "disease",
["importance"] = 3
},
["DISEASE_TREATMENT_STOP"] = {
["text"] = "diseaseTreatment_stop",
["title"] = "disease",
["importance"] = 3
},
["NAME_CHANGE"] = {
["text"] = "nameChange",
["title"] = "name",
["importance"] = 3
},
["NAME_ADDED"] = {
["text"] = "nameAdded",
["title"] = "name",
["importance"] = 3
},
["NAME_DELETED"] = {
["text"] = "nameDeleted",
["title"] = "name",
["importance"] = 3
},
["BOUGHT_ANIMALS_SINGLE"] = {
["text"] = "boughtAnimals_single",
["title"] = "movement",
["importance"] = 3
},
["BOUGHT_ANIMALS_MULTIPLE"] = {
["text"] = "boughtAnimals_multiple",
["title"] = "movement",
["importance"] = 3
},
["SOLD_ANIMALS_SINGLE"] = {
["text"] = "soldAnimals_single",
["title"] = "movement",
["importance"] = 3
},
["SOLD_ANIMALS_MULTIPLE"] = {
["text"] = "soldAnimals_multiple",
["title"] = "movement",
["importance"] = 3
},
["MOVED_ANIMALS_SOURCE_SINGLE"] = {
["text"] = "movedAnimalsFrom_single",
["title"] = "movement",
["importance"] = 3
},
["MOVED_ANIMALS_SOURCE_MULTIPLE"] = {
["text"] = "movedAnimalsFrom_multiple",
["title"] = "movement",
["importance"] = 3
},
["MOVED_ANIMALS_TARGET_SINGLE"] = {
["text"] = "movedAnimalsTo_single",
["title"] = "movement",
["importance"] = 3
},
["MOVED_ANIMALS_TARGET_MULTIPLE"] = {
["text"] = "movedAnimalsTo_multiple",
["title"] = "movement",
["importance"] = 3
},
["AI_MANAGER_BOUGHT_SINGLE"] = {
["text"] = "aiManager_boughtAnimals_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_BOUGHT_MULTIPLE"] = {
["text"] = "aiManager_boughtAnimals_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_SOLD_SINGLE"] = {
["text"] = "aiManager_soldAnimals_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_SOLD_MULTIPLE"] = {
["text"] = "aiManager_soldAnimals_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_CASTRATED_SINGLE"] = {
["text"] = "aiManager_castrated_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_CASTRATED_MULTIPLE"] = {
["text"] = "aiManager_castrated_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_NAMED_SINGLE"] = {
["text"] = "aiManager_named_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_NAMED_MULTIPLE"] = {
["text"] = "aiManager_named_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_SELL_SINGLE"] = {
["text"] = "aiManager_mark_sell_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_SELL_MULTIPLE"] = {
["text"] = "aiManager_mark_sell_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_CASTRATE_SINGLE"] = {
["text"] = "aiManager_mark_castrate_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_CASTRATE_MULTIPLE"] = {
["text"] = "aiManager_mark_castrate_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_DISEASE_SINGLE"] = {
["text"] = "aiManager_mark_disease_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_DISEASE_MULTIPLE"] = {
["text"] = "aiManager_mark_disease_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["INSEMINATION_SUCCESS"] = {
["text"] = "insemination_success",
["title"] = "insemination",
["importance"] = 3
},
["INSEMINATION_FAIL"] = {
["text"] = "insemination_fail",
["title"] = "insemination",
["importance"] = 3
},
["AI_MANAGER_INSEMINATED_SINGLE"] = {
["text"] = "aiManager_ai_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_INSEMINATED_MULTIPLE"] = {
["text"] = "aiManager_ai_multiple",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_INSEMINATED_SINGLE"] = {
["text"] = "aiManager_mark_ai_single",
["title"] = "aiManager",
["importance"] = 3
},
["AI_MANAGER_MARK_INSEMINATED_MULTIPLE"] = {
["text"] = "aiManager_mark_ai_multiple",
["title"] = "aiManager",
["importance"] = 3
}
}
================================================
FILE: src/RLSettings.lua
================================================
RLSettings = {}
local modDirectory = g_currentModDirectory
local modName = g_currentModName
local modSettingsDirectory = g_currentModSettingsDirectory
local modDirectoryPath = string.split(modDirectory, "/")
local baseDirectory = ""
for i = 1, #modDirectoryPath - 2 do
baseDirectory = baseDirectory .. (i == 1 and "" or "/") .. modDirectoryPath[i]
end
g_gui:loadProfiles(modDirectory .. "gui/guiProfiles.xml")
function RLSettings.onClickTagColour()
EarTagColourPickerDialog.show()
end
function RLSettings.onClickExportCSV()
local file = io.open(modSettingsDirectory .. "animals.csv", "w")
file:write("Type,Subtype,Country,Farm Id,Unique Id,Age,Health,Weight,Value,Value / kg,Pregnant,Expected Offspring,Lactating,Food,Water,Straw,Product,Manure,Liquid Manure")
local husbandrySystem = g_currentMission.husbandrySystem
local animalSystem = g_currentMission.animalSystem
for _, placeable in pairs(husbandrySystem.placeables) do
local animals = placeable:getClusters()
for _, animal in pairs(animals) do
local hasMonitor = animal.monitor.active or animal.monitor.removed
local foodInput = animal:getInput("food") * 24
local waterInput = animal:getInput("water") * 24
local strawInput = animal:getInput("straw") * 24
local manureOutput = animal:getOutput("manure") * 24
local liquidManureOutput = animal:getOutput("liquidManure") * 24
local milkOutput = animal:getOutput("milk") * 24
local palletsOutput = animal:getOutput("pallets") * 24
local productOutput = milkOutput > palletsOutput and milkOutput or palletsOutput
local value = animal:getSellPrice()
local valuePerKg = hasMonitor and (value / animal.weight) or "no monitor"
local expectedOffspring = animal.pregnancy ~= nil and animal.pregnancy.pregnancies ~= nil and #animal.pregnancy.pregnancies or 0
file:write(string.format("\n%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", animalSystem.types[animal.animalTypeIndex].name, animal.subType, RealisticLivestock.AREA_CODES[animal.birthday.country].code, animal.farmId, animal.uniqueId, animal.age, hasMonitor and animal.health or "no monitor", hasMonitor and animal.weight or "no monitor", value, valuePerKg, animal.isPregnant and "yes" or "no", expectedOffspring, (hasMonitor and (animal.isLactating and "yes" or "no") or "no monitor"), hasMonitor and foodInput or "no monitor", hasMonitor and waterInput or "no monitor", hasMonitor and strawInput or "no monitor", hasMonitor and productOutput or "no monitor", hasMonitor and manureOutput or "no monitor", hasMonitor and liquidManureOutput or "no monitor"))
end
end
file:close()
InfoDialog.show(modSettingsDirectory .. "animals.csv")
end
local function getFilesRecursively(path, parent)
local files = Files.new(path).files
for _, file in pairs(files) do
if file.isDirectory then
table.insert(parent.folders, { ["folders"] = {}, ["files"] = {}, ["name"] = file.filename, ["path"] = file.path })
getFilesRecursively(file.path, parent.folders[#parent.folders])
continue
end
local name = file.filename
if #name >= 4 and string.sub(name, #name - 3) == ".xml" then table.insert(parent.files, { ["name"] = name, ["valid"] = true }) end
end
end
function RLSettings.onClickChangeAnimalsXML()
local files = { { ["folders"] = {}, ["files"] = {}, ["name"] = baseDirectory, ["path"] = baseDirectory } }
getFilesRecursively(baseDirectory, files[1])
FileExplorerDialog.show(files, baseDirectory, RLSettings.onFileExplorerCallback)
end
function RLSettings.onFileExplorerCallback(path)
RLSettings.animalsXMLPath = path
end
RLSettings.SETTINGS = {
["deathEnabled"] = {
["index"] = 1,
["type"] = "BinaryOption",
["dynamicTooltip"] = true,
["default"] = 2,
["binaryType"] = "offOn",
["values"] = { false, true },
["callback"] = Animal.onSettingChanged
},
["accidentsChance"] = {
["index"] = 2,
["type"] = "MultiTextOption",
["default"] = 11,
["valueType"] = "float",
["values"] = { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0 },
["callback"] = Animal.onSettingChanged,
["dependancy"] = {
["name"] = "deathEnabled",
["state"] = 2
}
},
["foodScale"] = {
["index"] = 3,
["type"] = "MultiTextOption",
["default"] = 2,
["valueType"] = "float",
["values"] = { 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5 },
["callback"] = RealisticLivestock_PlaceableHusbandryFood.onSettingChanged
},
["maxDealerAnimals"] = {
["index"] = 4,
["type"] = "MultiTextOption",
["default"] = 4,
["valueType"] = "int",
["values"] = { 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200 },
["callback"] = AnimalSystem.onSettingChanged
},
["resetDealer"] = {
["index"] = 5,
["type"] = "Button",
["ignore"] = true,
["callback"] = AnimalSystem.onClickResetDealer
},
["tagColour"] = {
["index"] = 6,
["type"] = "Button",
["ignore"] = true,
["callback"] = RLSettings.onClickTagColour
},
["exportCSV"] = {
["index"] = 7,
["type"] = "Button",
["ignore"] = true,
["callback"] = RLSettings.onClickExportCSV
},
["maxNumMessages"] = {
["index"] = 7,
["type"] = "MultiTextOption",
["default"] = 5,
["valueType"] = "int",
["values"] = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3500, 4000, 4500, 5000 },
["callback"] = RealisticLivestock_PlaceableHusbandryAnimals.onSettingChanged
},
["diseasesEnabled"] = {
["index"] = 8,
["type"] = "BinaryOption",
["dynamicTooltip"] = true,
["default"] = 2,
["binaryType"] = "offOn",
["values"] = { false, true },
["callback"] = DiseaseManager.onSettingChanged
},
["diseasesChance"] = {
["index"] = 9,
["type"] = "MultiTextOption",
["default"] = 4,
["valueType"] = "float",
["values"] = { 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5 },
["callback"] = DiseaseManager.onSettingChanged,
["dependancy"] = {
["name"] = "diseasesEnabled",
["state"] = 2
}
},
["useCustomAnimals"] = {
["index"] = 10,
["type"] = "BinaryOption",
["dynamicTooltip"] = true,
["default"] = 1,
["binaryType"] = "offOn",
["values"] = { false, true },
["callback"] = RLSettings.onSettingChanged
},
["animalsXML"] = {
["index"] = 10,
["type"] = "Button",
["ignore"] = true,
["callback"] = RLSettings.onClickChangeAnimalsXML,
["dependancy"] = {
["name"] = "useCustomAnimals",
["state"] = 2
}
}
}
RLSettings.BinaryOption = nil
RLSettings.MultiTextOption = nil
RLSettings.Button = nil
function RLSettings.loadFromXMLFile()
if g_currentMission.missionInfo == nil or g_currentMission.missionInfo.savegameDirectory == nil then return end
local path = g_currentMission.missionInfo.savegameDirectory .. "/rlSettings.xml"
local xmlFile = XMLFile.loadIfExists("rlSettings", path)
if xmlFile ~= nil then
local key = "settings"
for name, setting in pairs(RLSettings.SETTINGS) do
if setting.ignore then continue end
setting.state = xmlFile:getInt(key .. "." .. name .. "#value", setting.default)
if setting.state > #setting.values then setting.state = #setting.values end
if name == "useCustomAnimals" and setting.state == 2 then RLSettings.animalsXMLPath = xmlFile:getString("settings.useCustomAnimals#path") end
end
xmlFile:delete()
end
end
function RLSettings.saveToXMLFile(name, state)
if RLSettings.isSaving or g_currentMission.missionInfo == nil or g_currentMission.missionInfo.savegameDirectory == nil then return end
if g_server ~= nil then
RLSettings.isSaving = true
local path = g_currentMission.missionInfo.savegameDirectory .. "/rlSettings.xml"
local xmlFile = XMLFile.create("rlSettings", path, "settings")
if xmlFile ~= nil then
for settingName, setting in pairs(RLSettings.SETTINGS) do
if setting.ignore then continue end
xmlFile:setInt("settings." .. settingName .. "#value", setting.state or setting.default)
if settingName == "useCustomAnimals" and setting.state == 2 and RLSettings.animalsXMLPath ~= nil then xmlFile:setString("settings.useCustomAnimals#path", RLSettings.animalsXMLPath) end
end
local saved = xmlFile:save(false, true)
xmlFile:delete()
end
end
RLSettings.isSaving = false
end
function RLSettings.initialize()
if g_server ~= nil then RLSettings.loadFromXMLFile() end
local settingsPage = g_inGameMenu.pageSettings
local scrollPanel = settingsPage.gameSettingsLayout
local sectionHeader, binaryOptionElement, multiOptionElement, buttonElement
for _, element in pairs(scrollPanel.elements) do
if element.name == "sectionHeader" and sectionHeader == nil then sectionHeader = element:clone(scrollPanel) end
if element.typeName == "Bitmap" then
if element.elements[1].typeName == "BinaryOption" and binaryOptionElement == nil then binaryOptionElement = element end
if element.elements[1].typeName == "MultiTextOption" and multiOptionElement == nil then multiOptionElement = element end
if element.elements[1].typeName == "Button" and buttonElement == nil then buttonElement = element end
end
if multiOptionElement and binaryOptionElement and sectionHeader and buttonElement then break end
end
if multiOptionElement == nil or binaryOptionElement == nil or sectionHeader == nil or buttonElement == nil then return end
RLSettings.BinaryOption = binaryOptionElement
RLSettings.MultiTextOption = multiOptionElement
RLSettings.Button = buttonElement
local prefix = "rl_settings_"
sectionHeader:setText(g_i18n:getText("rl_settings"))
local maxIndex = 0
for _, setting in pairs(RLSettings.SETTINGS) do maxIndex = maxIndex < setting.index and setting.index or maxIndex end
for i = 1, maxIndex do
for name, setting in pairs(RLSettings.SETTINGS) do
if setting.index ~= i then continue end
setting.state = setting.state or setting.default
local template = RLSettings[setting.type]:clone(scrollPanel)
local settingsPrefix = "rl_settings_" .. name .. "_"
template.id = nil
for _, element in pairs(template.elements) do
if element.typeName == "Text" then
element:setText(g_i18n:getText(settingsPrefix .. "label"))
element.id = nil
end
if element.typeName == setting.type then
if setting.type == "Button" then
element:setText(g_i18n:getText(settingsPrefix .. "text"))
element:applyProfile("rl_settingsButton")
element.isAlwaysFocusedOnOpen = false
element.focused = false
else
local texts = {}
if setting.binaryType == "offOn" then
texts[1] = g_i18n:getText("rl_settings_off")
texts[2] = g_i18n:getText("rl_settings_on")
else
for i, value in pairs(setting.values) do
if setting.valueType == "int" then
texts[i] = tostring(value)
elseif setting.valueType == "float" then
texts[i] = string.format("%.0f%%", value * 100)
else
texts[i] = g_i18n:getText(settingsPrefix .. "texts_" .. i)
end
end
end
element:setTexts(texts)
element:setState(setting.state)
if setting.dynamicTooltip then
element.elements[1]:setText(g_i18n:getText(settingsPrefix .. "tooltip_" .. setting.state))
else
element.elements[1]:setText(g_i18n:getText(settingsPrefix .. "tooltip"))
end
end
element.id = "rls_" .. name
element.onClickCallback = RLSettings.onSettingChanged
setting.element = element
if setting.dependancy then
local dependancy = RLSettings.SETTINGS[setting.dependancy.name]
if dependancy ~= nil and dependancy.element ~= nil then element:setDisabled(dependancy.state ~= setting.dependancy.state) end
end
end
end
end
end
end
function RLSettings.onSettingChanged(_, state, button)
if button == nil then button = state end
if button == nil or button.id == nil then return end
if not string.contains(button.id, "rls_") then return end
local name = string.sub(button.id, 5)
local setting = RLSettings.SETTINGS[name]
if setting == nil then return end
if setting.ignore then
if setting.callback then setting.callback() end
return
end
if setting.callback then setting.callback(name, setting.values[state]) end
setting.state = state
for _, s in pairs(RLSettings.SETTINGS) do
if s.dependancy and s.dependancy.name == name then
s.element:setDisabled(s.dependancy.state ~= state)
end
end
if setting.dynamicTooltip and setting.element ~= nil then setting.element.elements[1]:setText(g_i18n:getText("rl_settings_" .. name .. "_tooltip_" .. setting.state)) end
if g_server ~= nil then
--RLSettings.saveToXMLFile(name, state)
else
--RL_BroadcastSettingsEvent.sendEvent(name)
end
end
function RLSettings.applyDefaultSettings()
if g_server == nil then
--RL_BroadcastSettingsEvent.sendEvent()
else
for name, setting in pairs(RLSettings.SETTINGS) do
if setting.ignore then continue end
if setting.callback ~= nil then setting.callback(name, setting.values[setting.state]) end
if setting.dynamicTooltip and setting.element ~= nil then setting.element.elements[1]:setText(g_i18n:getText("rl_settings_" .. name .. "_tooltip_" .. setting.state)) end
for _, s in pairs(RLSettings.SETTINGS) do
if s.dependancy and s.dependancy.name == name and s.element ~= nil then
s.element:setDisabled(s.dependancy.state ~= state)
end
end
end
end
end
function RLSettings.getAnimalsXMLPath()
if RLSettings.customAnimals == nil then return nil end
return RLSettings.customAnimals.basePath .. RLSettings.customAnimals.animals
end
function RLSettings.getFillTypesXMLPath()
if RLSettings.customAnimals == nil then return nil end
return RLSettings.customAnimals.basePath .. RLSettings.customAnimals.fillTypes
end
function RLSettings.getTranslationsFolderPath()
if RLSettings.customAnimals == nil then return nil end
return RLSettings.customAnimals.basePath .. RLSettings.customAnimals.translations
end
function RLSettings.getAnimalsBasePath()
if RLSettings.customAnimals == nil then return nil end
return RLSettings.customAnimals.basePath
end
function RLSettings.getOverrideVanillaAnimals()
if RLSettings.customAnimals == nil then return false end
return RLSettings.customAnimals.override
end
function RLSettings.validateCustomAnimalsConfiguration()
if RLSettings.SETTINGS.useCustomAnimals.state == 1 or RLSettings.animalsXMLPath == nil or g_currentMission.missionDynamicInfo.isMultiplayer then return end
local xmlFile = XMLFile.loadIfExists("customAnimalsConfig", RLSettings.animalsXMLPath)
if xmlFile == nil then return end
local basePath
local splitPath = string.split(RLSettings.animalsXMLPath, "/")
for i = #splitPath, 1, -1 do
local path = table.concat(splitPath, "/", 1, i)
if path == baseDirectory then
basePath = table.concat(splitPath, "/", 1, i + 1) .. "/"
break
end
end
if basePath == nil then return end
RLSettings.customAnimals = {
["basePath"] = basePath,
["animals"] = xmlFile:getString("RealisticLivestock#animals", "animals.xml"),
["fillTypes"] = xmlFile:getString("RealisticLivestock#fillTypes", "fillTypes.xml"),
["translations"] = xmlFile:getString("RealisticLivestock#translations", "l10n/"),
["override"] = xmlFile:getBool("RealisticLivestock#override", false)
}
xmlFile:delete()
local l10nNames = {
g_languageShort,
"en",
"de"
}
local l10nXML
for _, l10nName in pairs(l10nNames) do
l10nXML = XMLFile.loadIfExists("l10n", basePath .. RLSettings.customAnimals.translations .. "_" .. l10nName .. ".xml")
if l10nXML ~= nil then break end
end
if l10nXML ~= nil then
l10nXML:iterate("l10n.texts.text", function(_, key)
local name = l10nXML:getString(key .. "#name")
local text = l10nXML:getString(key .. "#text")
if name ~= nil and text ~= nil then
if g_i18n:hasModText(name) then
printWarning("Warning: Duplicate l10n entry \'" .. name .. "\'. Ignoring this definition.")
else
g_i18n:setText(name, text:gsub("\r\n", "\n"))
end
end
end)
l10nXML:delete()
end
local fillTypesXML = loadXMLFile("fillTypes", basePath .. RLSettings.customAnimals.fillTypes)
g_fillTypeManager:loadFillTypes(fillTypesXML, basePath, false, modName)
end
================================================
FILE: src/RL_BroadcastSettingsEvent.lua
================================================
RL_BroadcastSettingsEvent = {}
local RL_BroadcastSettingsEvent_mt = Class(RL_BroadcastSettingsEvent, Event)
InitEventClass(RL_BroadcastSettingsEvent, "RL_BroadcastSettingsEvent")
function RL_BroadcastSettingsEvent.emptyNew()
local self = Event.new(RL_BroadcastSettingsEvent_mt)
return self
end
function RL_BroadcastSettingsEvent.new(setting)
local self = RL_BroadcastSettingsEvent.emptyNew()
self.setting = setting
return self
end
function RL_BroadcastSettingsEvent:readStream(streamId, connection)
local readAll = streamReadBool(streamId)
if readAll then
for _, setting in pairs(RLSettings.SETTINGS) do
if setting.ignore then continue end
local name = streamReadString(streamId)
local state = streamReadUInt8(streamId)
RLSettings.SETTINGS[name].state = state
end
else
local name = streamReadString(streamId)
local state = streamReadUInt8(streamId)
RLSettings.SETTINGS[name].state = state
self.setting = name
end
self:run(connection)
end
function RL_BroadcastSettingsEvent:writeStream(streamId, connection)
streamWriteBool(streamId, self.setting == nil)
if self.setting == nil then
for name, setting in pairs(RLSettings.SETTINGS) do
if setting.ignore then continue end
streamWriteString(streamId, name)
streamWriteUInt8(streamId, setting.state)
end
else
local setting = RLSettings.SETTINGS[self.setting]
streamWriteString(streamId, self.setting)
streamWriteUInt8(streamId, setting.state)
end
end
function RL_BroadcastSettingsEvent:run(connection)
if self.setting == nil then
for name, setting in pairs(RLSettings.SETTINGS) do
if setting.ignore then continue end
setting.element:setState(setting.state)
if setting.callback ~= nil then setting.callback(name, setting.values[setting.state]) end
end
else
local setting = RLSettings.SETTINGS[self.setting]
if setting.element ~= nil then setting.element:setState(setting.state) end
if setting.callback ~= nil then setting.callback(self.setting, setting.values[setting.state]) end
if setting.dynamicTooltip and setting.element ~= nil then setting.element.elements[1]:setText(g_i18n:getText("rl_settings_" .. self.setting .. "_tooltip_" .. setting.state)) end
for _, s in pairs(RLSettings.SETTINGS) do
if s.dependancy and s.dependancy.name == self.setting and s.element ~= nil then
s.element:setDisabled(s.dependancy.state ~= state)
end
end
if g_server ~= nil then RLSettings.saveToXMLFile() end
end
end
function RL_BroadcastSettingsEvent.sendEvent(setting)
if g_server ~= nil then
g_server:broadcastEvent(RL_BroadcastSettingsEvent.new(setting))
else
g_client:getServerConnection():sendEvent(RL_BroadcastSettingsEvent.new(setting))
end
end
================================================
FILE: src/RealisticLivestock.lua
================================================
RealisticLivestock = {}
local modDirectory = g_currentModDirectory
local hasLoaded = false
RealisticLivestock.FONTS = g_fontManager:loadFontsFromXMLFile(g_currentModDirectory .. "fonts/fonts.xml", g_currentModDirectory)
RealisticLivestock.MARKS = {
["AI_MANAGER_SELL"] = {
["key"] = "AI_MANAGER_SELL",
["active"] = false,
["priority"] = 3,
["text"] = "aiManager_sell"
},
["AI_MANAGER_CASTRATE"] = {
["key"] = "AI_MANAGER_CASTRATE",
["active"] = false,
["priority"] = 5,
["text"] = "aiManager_castrate"
},
["AI_MANAGER_DISEASE"] = {
["key"] = "AI_MANAGER_DISEASE",
["active"] = false,
["priority"] = 2,
["text"] = "aiManager_disease"
},
["AI_MANAGER_INSEMINATE"] = {
["key"] = "AI_MANAGER_INSEMINATE",
["active"] = false,
["priority"] = 4,
["text"] = "aiManager_ai"
},
["PLAYER"] = {
["key"] = "PLAYER",
["active"] = false,
["priority"] = 1,
["text"] = "player"
}
}
RealisticLivestock.MAP_TO_AREA_CODE = {
["Riverbend Springs"] = 2,
["Hutan Pantai"] = 3,
["Zielonka"] = 5,
["Zacieczki"] = 5,
["Szpakowo"] = 5,
["Pallegney"] = 4,
["Oberschwaben"] = 6,
["Starowies"] = 5,
["Lipinki"] = 5,
["Rhönplateu"] = 6,
["Schwesing Bahnhof"] = 6,
["Riverview"] = 1,
["Sobolewo"] = 5,
["Tässi Farm"] = 8,
["HORSCH AgroVation"] = 10,
["New Bartelshagenn"] = 6,
["HermannsHausen"] = 5,
["Oak Bridge Farm"] = 1,
["Calmsden Farm"] = 1,
["Frankenmuth Farming Map"] = 2,
["North Frisian 25"] = 6,
["Alma, Missouri"] = 2,
["Michigan Map"] = 2
}
RealisticLivestock.AREA_CODES = {
[1] = {
["code"] = "UK",
["country"] = "United Kingdom"
},
[2] = {
["code"] = "US",
["country"] = "United States"
},
[3] = {
["code"] = "CH",
["country"] = "China"
},
[4] = {
["code"] = "FR",
["country"] = "France"
},
[5] = {
["code"] = "PL",
["country"] = "Poland"
},
[6] = {
["code"] = "DE",
["country"] = "Germany"
},
[7] = {
["code"] = "CA",
["country"] = "Canada"
},
[8] = {
["code"] = "EE",
["country"] = "Estonia"
},
[9] = {
["code"] = "IT",
["country"] = "Italy"
},
[10] = {
["code"] = "CZ",
["country"] = "Czech Republic"
},
[11] = {
["code"] = "RU",
["country"] = "Russia"
},
[12] = {
["code"] = "SW",
["country"] = "Sweden"
},
[13] = {
["code"] = "NO",
["country"] = "Norway"
},
[14] = {
["code"] = "FI",
["country"] = "Finland"
},
[15] = {
["code"] = "JP",
["country"] = "Japan"
},
[16] = {
["code"] = "SP",
["country"] = "Spain"
}
}
RealisticLivestock.ALPHABET = {
["0"] = 0,
["1"] = 1,
["2"] = 2,
["3"] = 3,
["4"] = 4,
["5"] = 5,
["6"] = 6,
["7"] = 7,
["8"] = 8,
["9"] = 9,
["A"] = 10,
["B"] = 11,
["C"] = 12,
["D"] = 13,
["E"] = 14,
["F"] = 15,
["G"] = 16,
["H"] = 17,
["I"] = 18,
["J"] = 19,
["K"] = 20,
["L"] = 21,
["M"] = 22,
["N"] = 23,
["O"] = 24,
["P"] = 25,
["Q"] = 26,
["R"] = 27,
["S"] = 28,
["T"] = 29,
["U"] = 30,
["V"] = 31,
["W"] = 32,
["X"] = 33,
["Y"] = 34,
["Z"] = 35,
["/"] = 36,
["-"] = 37
}
RealisticLivestock.NUM_CHARACTERS = 64
RealisticLivestock.DAYS_PER_MONTH = {
[1] = 31,
[2] = 28,
[3] = 31,
[4] = 30,
[5] = 31,
[6] = 30,
[7] = 31,
[8] = 31,
[9] = 30,
[10] = 31,
[11] = 30,
[12] = 31
}
RealisticLivestock.START_YEAR = {
["FULL"] = 2024,
["PARTIAL"] = 24
}
table.insert(FinanceStats.statNames, "herdsmanWages")
FinanceStats.statNameToIndex["herdsmanWages"] = #FinanceStats.statNames
table.insert(FinanceStats.statNames, "semenPurchase")
FinanceStats.statNameToIndex["semenPurchase"] = #FinanceStats.statNames
table.insert(FinanceStats.statNames, "medicine")
FinanceStats.statNameToIndex["medicine"] = #FinanceStats.statNames
function RealisticLivestock.loadMap()
RealisticLivestock.mapAreaCode = RealisticLivestock.MAP_TO_AREA_CODE[g_currentMission.missionInfo.mapTitle] or 1
g_overlayManager:addTextureConfigFile(modDirectory .. "gui/helpicons.xml", "rlHelpIcons")
g_overlayManager:addTextureConfigFile(modDirectory .. "gui/icons.xml", "realistic_livestock")
g_overlayManager:addTextureConfigFile(modDirectory .. "gui/fileTypeIcons.xml", "fileTypeIcons")
g_rlConsoleCommandManager = RLConsoleCommandManager.new()
g_diseaseManager = DiseaseManager.new()
MoneyType.HERDSMAN_WAGES = MoneyType.register("herdsmanWages", "rl_ui_herdsmanWages")
MoneyType.LAST_ID = MoneyType.LAST_ID + 1
MoneyType.SEMEN_PURCHASE = MoneyType.register("semenPurchase", "rl_ui_semenPurchase")
MoneyType.LAST_ID = MoneyType.LAST_ID + 1
MoneyType.MEDICINE = MoneyType.register("medicine", "rl_ui_medicine")
MoneyType.LAST_ID = MoneyType.LAST_ID + 1
end
addModEventListener(RealisticLivestock)
function RealisticLivestock.getMapCountryCode()
local areaCode = RealisticLivestock.AREA_CODES[RealisticLivestock.mapAreaCode]
if areaCode ~= nil then return areaCode.code end
return "UK"
end
function RealisticLivestock.getMapCountryIndex()
return RealisticLivestock.mapAreaCode or 1
end
function RealisticLivestock.formatAge(age)
local years = math.floor(age / 12)
local months = age % 12
local monthsString = months == 1 and g_i18n:getText("rl_ui_month") or g_i18n:getText("rl_ui_months")
if years > 0 then return string.format("%s %s, %s %s", years, years == 1 and g_i18n:getText("rl_ui_year") or g_i18n:getText("rl_ui_years"), months, monthsString) end
return string.format("%s %s", months, monthsString)
end
-- #################################################
-- THE MAJORITY OF THIS FILE IS LEGACY AND IS UNUSED
-- #################################################
function RealisticLivestock:updateReproduction(spec, cluster, numNewAnimals, freeSlots, isServer)
local totalOffspring = 0
local totalParents = cluster.numAnimals
local parentAge = cluster.age
local parentHealth = cluster.health
local animalType = spec.animalTypeIndex
local lactatingAnimals = 0
local isParent = cluster.isParent
for i = 1, totalParents do
local childNumProb = 500
local childNum = 1
local noChildProb = 50
local deathChance = 10
local parentDied = false
local deathChanceProb = math.random(1, 1000)
if parentHealth <= 60 then
childNumProb = math.random(0, 250)
deathChance = 135
elseif parentHealth <= 75 then
childNumProb = math.random(10, 500)
deathChance = 60
elseif parentHealth <= 90 then
childNumProb = math.random(50, 900)
deathChance = 15
else
childNumProb = math.random(70, 1000)
deathChance = 3
end
if animalType == AnimalType.COW then
if parentAge <= 28 then
noChildProb = 40
deathChance = deathChance + 4
elseif parentAge <= 36 then
noChildProb = 32
deathChance = deathChance - 10
elseif parentAge <= 48 then
noChildProb = 24
deathChance = deathChance -8
elseif parentAge <= 60 then
noChildProb = 21
deathChance = deathChance - 2
elseif parentAge <= 84 then
noChildProb = 70
deathChance = deathChance + 4
elseif parentAge <= 108 then
noChildProb = 220
deathChance = deathChance + 8
elseif parentAge <= 132 then
noChildProb = 460
deathChance = deathChance + 20
else
noChildProb = 1000
end
if deathChanceProb <= deathChance then
noChildProb = noChildProb * (1.1 + math.random())
parentDied = true
end
if childNumProb <= noChildProb then
childNum = 0
if not parentDied and math.random() <= 0.08 then lactatingAnimals = lactatingAnimals + 1 end
elseif childNumProb >= 950 and childNumProb <= 997 then
childNum = 2
lactatingAnimals = lactatingAnimals + 1
elseif childNumProb >= 997 then
childNum = 3
lactatingAnimals = lactatingAnimals + 1
else
lactatingAnimals = lactatingAnimals + 1
end
elseif animalType == AnimalType.PIG then
if parentAge <= 12 then
noChildProb = 60
deathChance = deathChance + 3
elseif parentAge <= 36 then
noChildProb = 40
deathChance = deathChance - 4
elseif parentAge <= 60 then
noChildProb = 0130
deathChance = deathChance - 3
elseif parentAge <= 80 then
noChildProb = 250
deathChance = deathChance + 2
elseif parentAge <= 96 then
noChildProb = 460
deathChance = deathChance + 10
else
noChildProb = 1000
end
if deathChanceProb <= deathChance then
noChildProb = noChildProb * (1.1 + math.random())
parentDied = true
end
if childNumProb <= noChildProb then
childNum = 0
elseif childNumProb <= 90 then
childNum = math.random(1, 6)
elseif childNumProb <= 240 then
childNum = math.random(7, 10)
elseif childNumProb <= 850 then
childNum = math.random(11, 13)
else
childNum = math.random(14, 16)
end
elseif animalType == AnimalType.HORSE then
if parentAge <= 12 then
noChildProb = 60
deathChance = deathChance - 7
elseif parentAge <= 48 then
noChildProb = 50
deathChance = deathChance - 6
elseif parentAge <= 60 then
noChildProb = 65
deathChance = deathChance - 4
elseif parentAge <= 84 then
noChildProb = 85
deathChance = deathChance - 1
elseif parentAge <= 108 then
noChildProb = 120
deathChance = deathChance + 1
elseif parentAge <= 132 then
noChildProb = 160
deathChance = deathChance + 3
elseif parentAge <= 156 then
noChildProb = 220
deathChance = deathChance + 6
elseif parentAge <= 180 then
noChildProb = 290
deathChance = deathChance + 8
elseif parentAge <= 216 then
noChildProb = 460
deathChance = deathChance + 10
elseif parentAge <= 240 then
noChildProb = 630
deathChance = deathChance + 15
elseif parentAge <= 264 then
noChildProb = 850
deathChance = deathChance + 20
else
noChildProb = 1000
end
if deathChanceProb <= deathChance then
noChildProb = noChildProb * (1.1 + math.random())
parentDied = true
end
if childNumProb <= noChildProb then
childNum = 0
elseif childNumProb >= 955 and childNumProb <= 997 then
childNum = 2
elseif childNumProb >= 997 then
childNum = 3
end
elseif animalType == AnimalType.CHICKEN then
if parentAge <= 12 then
noChildProb = 400
elseif parentAge <= 24 then
noChildProb = 440
elseif parentAge <= 36 then
noChildProb = 500
elseif parentAge <= 48 then
noChildProb = 580
elseif parentAge <= 60 then
noChildProb = 675
elseif parentAge <= 84 then
noChildProb = 820
elseif parentAge <= 120 then
noChildProb = 960
else
noChildProb = 1000
end
if childNumProb <= noChildProb then
childNum = 0
elseif childNumProb <= 480 then
childNum = math.random(1, 5)
elseif childNumProb <= 760 then
childNum = math.random(5, 7)
elseif childNumProb <= 920 then
childNum = math.random(7, 9)
else
childNum = math.random(10, 12)
end
elseif animalType == AnimalType.SHEEP then
if parentAge <= 18 then
noChildProb = 280
deathChance = deathChance - 1
elseif parentAge <= 24 then
noChildProb = 220
deathChance = deathChance - 3
elseif parentAge <= 36 then
noChildProb = 180
deathChance = deathChance - 5
elseif parentAge <= 72 then
noChildProb = 140
deathChance = deathChance - 7
elseif parentAge <= 84 then
noChildProb = 320
deathChance = deathChance - 1
elseif parentAge <= 108 then
noChildProb = 600
deathChance = deathChance + 5
elseif parentAge <= 120 then
noChildProb = 870
deathChance = deathChance + 10
else
noChildProb = 1000
end
if deathChanceProb <= deathChance then
noChildProb = noChildProb * (1.1 + math.random())
parentDied = true
end
if childNumProb <= noChildProb then
childNum = 0
elseif not isParent and childNumProb <= 870 then
childNum = 1
elseif parentAge < 36 and childNumProb >= 500 and childNumProb <= 965 then
childNum = 2
elseif parentAge >= 36 and parentAge < 72 and childNumProb >= 350 and childNumProb <= 920 then
childNum = 2
elseif parentAge >= 72 and childNumProb <= 980 then
childNum = 2
elseif parentAge < 36 and childNumProb >= 965 then
childNum = 3
elseif parentAge >= 36 and parentAge < 72 and childNumProb >= 920 then
childNum = 3
elseif parentAge >= 72 and childNumProb >= 980 then
childNum = 3
end
end
print("parent #" .. (i + 1) .. ": ".. childNum .. " children")
totalOffspring = totalOffspring + childNum
if parentDied == true then
RealisticLivestock.KillAnimals(spec, cluster, 1)
print("animal died in childbirth")
end
end
local animalTypeText = ""
print(" --- ")
if animalType == AnimalType.PIG then
animalTypeText = "piglets"
if totalOffspring == 1 then animalTypeText = "piglet" end
print("PIGS")
elseif cluster.subType == "COW_WATERBUFFALO" then
animalTypeText = "buffalos"
if totalOffspring == 1 then animalTypeText = "buffalo" end
print("WATER BUFFALOS")
elseif animalType == AnimalType.COW then
animalTypeText = "calves"
if totalOffspring == 1 then animalTypeText = "calf" end
print("CATTLE")
elseif cluster.subType == "GOAT" then
animalTypeText = "goats"
if totalOffspring == 1 then animalTypeText = "goat" end
print("GOATS")
elseif animalType == AnimalType.SHEEP then
animalTypeText = "lambs"
if totalOffspring == 1 then animalTypeText = "lamb" end
print("SHEEP")
elseif animalType == AnimalType.HORSE then
animalTypeText = "foals"
if totalOffspring == 1 then animalTypeText = "foal" end
print("HORSES")
elseif animalType == AnimalType.CHICKEN then
animalTypeText = "chicks"
if totalOffspring == 1 then animalTypeText = "chick" end
print("CHICKEN")
end
print(totalParents .. " total parents")
print(totalOffspring .. " total offspring")
print(lactatingAnimals .. " lactating animals")
cluster.lactatingAnimals = lactatingAnimals
local animalsToSell = 0
local subType = g_currentMission.animalSystem:getSubTypeByIndex(cluster:getSubTypeIndex())
local farmIndex = spec:getOwnerFarmId()
if freeSlots - totalOffspring < 0 then
animalsToSell = totalOffspring - freeSlots
end
cluster.monthsSinceLastBirth = 0
if totalOffspring > 0 then cluster.isParent = true end
local msgText = totalOffspring .. " " .. animalTypeText .. " born"
totalOffspring = totalOffspring - animalsToSell
if animalsToSell > 0 then
local animalPrice = subType.sellPrice:get(0) * 0.4
local totalAnimalPrice = animalPrice * animalsToSell
local farm = g_farmManager:getFarmById(farmIndex)
msgText = msgText .. ", " .. animalsToSell .. " sold due to overcrowding for £" .. math.floor(totalAnimalPrice)
if isServer then
g_currentMission:addMoneyChange(totalAnimalPrice, farmIndex, MoneyType.SOLD_ANIMALS, true)
else
g_client:getServerConnection():sendEvent(MoneyChangeEvent.new(totalAnimalPrice, MoneyType.SOLD_ANIMALS, farmIndex))
end
if farm ~= nil then
farm:changeBalance(totalAnimalPrice, MoneyType.SOLD_ANIMALS)
end
print(animalsToSell .. " for £" .. animalPrice .. " each.")
end
if totalOffspring >= 1 then
local numMale = math.random(0, totalOffspring)
if numMale >= 1 then
local maleCluster = g_currentMission.animalSystem:createClusterFromSubTypeIndex(cluster:getSubTypeIndex() + 1)
maleCluster.numAnimals = numMale
maleCluster.monthsSinceLastBirth = 0
maleCluster.isParent = false
maleCluster.lactatingAnimals = 0
maleCluster.gender = "male"
spec.clusterSystem:addPendingAddCluster(maleCluster)
end
if totalOffspring - numMale >= 1 then
local femaleCluster = g_currentMission.animalSystem:createClusterFromSubTypeIndex(cluster:getSubTypeIndex())
femaleCluster.numAnimals = totalOffspring - numMale
femaleCluster.monthsSinceLastBirth = 0
femaleCluster.isParent = false
femaleCluster.lactatingAnimals = 0
femaleCluster.gender = "female"
spec.clusterSystem:addPendingAddCluster(femaleCluster)
end
local animalTypeReal = g_currentMission.animalSystem:getTypeByIndex(subType.typeIndex)
if animalTypeReal.statsBreedingName ~= nil then
local stats = g_currentMission:farmStats(farmIndex)
stats:updateStats(animalTypeReal.statsBreedingName, totalOffspring)
end
end
if totalOffspring > 0 or animalsToSell > 0 then g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText) end
end
function RealisticLivestock.KillAnimals(spec, cluster, amount)
cluster.numAnimals = cluster.numAnimals - amount
if cluster.numAnimals <= 0 then
local husbandryAnimals = spec.spec_husbandryAnimals
for i, selectedCluster in ipairs(husbandryAnimals.clusterSystem.clusters) do
if selectedCluster == cluster then
table.remove(husbandryAnimals.clusterSystem.clusters, i)
break
end
end
end
spec:updateVisualAnimals()
end
-- Animals can die from low health
function RealisticLivestock.CalculateLowHealthMonthlyAnimalDeaths(spec, cluster)
if cluster.numAnimals <= 0 then
return
end
local numAnimalsToDispose = 0
local numAnimals = cluster.numAnimals
local deathChance = 0.01
local health = cluster.health
if health >= 80 then
return
end
deathChance = 0.8 - (health / 100)
if math.random() <= deathChance then
if cluster.age < 6 then health = health - 10 end
numAnimalsToDispose = math.random(math.max(1, (0.8 - (health / 100)) * numAnimals))
if numAnimalsToDispose < 1 then
numAnimalsToDispose = 1
end
if numAnimalsToDispose > numAnimals then
numAnimalsToDispose = numAnimals
end
RealisticLivestock.KillAnimals(spec, cluster, numAnimalsToDispose)
local animalTypeText = ""
local animalType = spec.animalTypeIndex
if animalType == AnimalType.PIG and cluster.age < 6 then
animalTypeText = "piglets"
if numAnimalsToDispose == 1 then animalTypeText = "piglet" end
elseif animalType == AnimalType.PIG then
animalTypeText = "pigs"
if numAnimalsToDispose == 1 then animalTypeText = "pig" end
end
if cluster.subType == "COW_WATERBUFFALO" then
animalTypeText = "buffalos"
if numAnimalsToDispose == 1 then animalTypeText = "buffalo" end
elseif animalType == AnimalType.COW and cluster.age < 12 then
animalTypeText = "calves"
if numAnimalsToDispose == 1 then animalTypeText = "calf" end
elseif animalType == AnimalType.COW then
animalTypeText = "cows"
if numAnimalsToDispose == 1 then animalTypeText = "cow" end
end
if cluster.subType == "GOAT" then
animalTypeText = "goats"
if numAnimalsToDispose == 1 then animalTypeText = "goat" end
elseif animalType == AnimalType.SHEEP and cluster.age < 6 then
animalTypeText = "lambs"
if numAnimalsToDispose == 1 then animalTypeText = "lamb" end
elseif animalType == AnimalType.SHEEP then
animalTypeText = "sheep"
end
if animalType == AnimalType.HORSE and cluster.age < 12 then
animalTypeText = "foals"
if numAnimalsToDispose == 1 then animalTypeText = "foal" end
elseif animalType == AnimalType.HORSE then
animalTypeText = "horses"
if numAnimalsToDispose == 1 then animalTypeText = "horse" end
end
if animalType == AnimalType.CHICKEN and cluster.age < 6 then
animalTypeText = "chicks"
if numAnimalsToDispose == 1 then animalTypeText = "chick" end
elseif animalType == AnimalType.CHICKEN then
animalTypeText = "chickens"
if numAnimalsToDispose == 1 then animalTypeText = "chicken" end
end
msgText = numAnimalsToDispose .. " " .. animalTypeText .. " died due to low health"
if numAnimalsToDispose >= 1 then g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText) end
end
end
-- Animals can die from old age
function RealisticLivestock.CalculateOldAgeMonthlyAnimalDeaths(spec, cluster)
if cluster.numAnimals <= 0 then
return
end
local animalType = spec.animalTypeIndex
local numAnimalsToDispose = 0
local numAnimals = cluster.numAnimals
local deathChance = 0.01
local age = cluster.age
local minAge = 20000
local maxAge = 30000
if animalType == AnimalType.COW then
-- cattle old age min: 15y (180m)
-- cattle old age max: 20y (240m)
minAge = 180
maxAge = 240
elseif animalType == AnimalType.SHEEP then
-- sheep old age min: 10y (120m)
-- sheep old age max: 12y (144m)
minAge = 120
maxAge = 144
elseif animalType == AnimalType.HORSE then
-- horse old age min: 25y (300m)
-- horse old age max: 30y (360m)
minAge = 300
maxAge = 360
elseif animalType == AnimalType.PIG then
-- pig old age min: 15y (180m)
-- pig old age max: 20y (240m)
minAge = 180
maxAge = 240
elseif animalType == AnimalType.CHICKEN then
-- chicken old age min: 5y (60m)
-- chicken old age max: 8y (96m)
minAge = 60
maxAge = 96
end
if age < minAge then
return
end
deathChance = 0.7 - ((maxAge - age) / 100)
if math.random() <= deathChance then
numAnimalsToDispose = math.random((0.61 - ((maxAge - age) / 100)) * numAnimals)
if numAnimalsToDispose < 1 then
numAnimalsToDispose = 1
end
if numAnimalsToDispose > numAnimals then
numAnimalsToDispose = numAnimals
end
RealisticLivestock.KillAnimals(spec, cluster, numAnimalsToDispose)
local animalTypeText = ""
local animalType = spec.animalTypeIndex
if animalType == AnimalType.PIG then
animalTypeText = "pigs"
if numAnimalsToDispose == 1 then animalTypeText = "pig" end
end
if cluster.subType == "COW_WATERBUFFALO" then
animalTypeText = "buffalos"
if numAnimalsToDispose == 1 then animalTypeText = "buffalo" end
elseif animalType == AnimalType.COW then
animalTypeText = "cows"
if numAnimalsToDispose == 1 then animalTypeText = "cow" end
end
if cluster.subType == "GOAT" then
animalTypeText = "goats"
if numAnimalsToDispose == 1 then animalTypeText = "goat" end
elseif animalType == AnimalType.SHEEP then
animalTypeText = "sheep"
end
if animalType == AnimalType.HORSE then
animalTypeText = "horses"
if numAnimalsToDispose == 1 then animalTypeText = "horse" end
end
if animalType == AnimalType.CHICKEN then
animalTypeText = "chickens"
if numAnimalsToDispose == 1 then animalTypeText = "chicken" end
end
msgText = numAnimalsToDispose .. " " .. animalTypeText .. " died due to old age"
if numAnimalsToDispose >= 1 then g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText) end
end
end
-- Animals can die randomly regardless of health such as due to broken legs - will be sold at a reduced price (lower quality meat)
function RealisticLivestock.CalculateRandomMonthlyAnimalDeaths(spec, cluster, isServer)
if cluster.numAnimals <= 0 then
return
end
local animalType = spec.animalTypeIndex
local numAnimalsToDispose = 0
local animalsCanBeSold = true
local numAnimals = cluster.numAnimals
local deathChance = 0.01
local temp = spec.minTemp
if animalType == AnimalType.COW then
deathChance = 0.018
if cluster.age < 6 then
deathChance = 0.028
elseif cluster.age < 18 then
deathChance = 0.02
end
elseif animalType == AnimalType.SHEEP then
deathChance = 0.012
if cluster.age < 3 then
deathChance = 0.023
elseif cluster.age < 8 then
deathChance = 0.015
end
elseif animalType == AnimalType.HORSE then
deathChance = 0.013
elseif animalType == AnimalType.PIG then
deathChance = 0.005
if cluster.age < 3 then
deathChance = 0.038
elseif cluster.age < 6 then
deathChance = 0.012
end
elseif animalType == AnimalType.CHICKEN then
if cluster.age < 6 then
deathChance = 0.003
else
deathChance = 0.004
end
animalsCanBeSold = false
end
-- animals are more likely to die in cold weather, especially young animals due to ice, pneumonia etc
if temp ~= nil and temp < 10 and temp >= 0 then
deathChance = deathChance * (1 + (1 - (temp / 10)))
elseif temp ~= nil and temp < 0 then
deathChance = deathChance * (1 + (1 - (temp / 8)))
end
if math.random() <= deathChance then
numAnimalsToDispose = 1
if numAnimals >= 10 and math.random() >= 0.92 then
numAnimalsToDispose = math.max(math.random(2, math.floor(numAnimals / 3)), 4)
end
end
if numAnimalsToDispose >= 1 then
RealisticLivestock.KillAnimals(spec, cluster, numAnimalsToDispose)
local totalAnimalPrice = 0
if animalsCanBeSold then
local subType = g_currentMission.animalSystem:getSubTypeByIndex(cluster:getSubTypeIndex())
local farmIndex = spec:getOwnerFarmId()
local animalPrice = subType.sellPrice:get(cluster.age) * 0.2
totalAnimalPrice = animalPrice * numAnimalsToDispose
local farm = g_farmManager:getFarmById(farmIndex)
if isServer then
g_currentMission:addMoneyChange(totalAnimalPrice, farmIndex, MoneyType.SOLD_ANIMALS, true)
else
g_client:getServerConnection():sendEvent(MoneyChangeEvent.new(totalAnimalPrice, MoneyType.SOLD_ANIMALS, farmIndex))
end
if farm ~= nil then
farm:changeBalance(totalAnimalPrice, MoneyType.SOLD_ANIMALS)
end
print(numAnimalsToDispose .. " for £" .. animalPrice .. " each.")
end
local animalTypeText = ""
local animalType = spec.animalTypeIndex
if animalType == AnimalType.PIG and cluster.age < 6 then
animalTypeText = "piglets"
if numAnimalsToDispose == 1 then animalTypeText = "piglet" end
elseif animalType == AnimalType.PIG then
animalTypeText = "pigs"
if numAnimalsToDispose == 1 then animalTypeText = "pig" end
end
if cluster.subType == "COW_WATERBUFFALO" then
animalTypeText = "buffalos"
if numAnimalsToDispose == 1 then animalTypeText = "buffalo" end
elseif animalType == AnimalType.COW and cluster.age < 12 then
animalTypeText = "calves"
if numAnimalsToDispose == 1 then animalTypeText = "calf" end
elseif animalType == AnimalType.COW then
animalTypeText = "cows"
if numAnimalsToDispose == 1 then animalTypeText = "cow" end
end
if cluster.subType == "GOAT" then
animalTypeText = "goats"
if numAnimalsToDispose == 1 then animalTypeText = "goat" end
elseif animalType == AnimalType.SHEEP and cluster.age < 6 then
animalTypeText = "lambs"
if numAnimalsToDispose == 1 then animalTypeText = "lamb" end
elseif animalType == AnimalType.SHEEP then
animalTypeText = "sheep"
end
if animalType == AnimalType.HORSE and cluster.age < 12 then
animalTypeText = "foals"
if numAnimalsToDispose == 1 then animalTypeText = "foal" end
elseif animalType == AnimalType.HORSE then
animalTypeText = "horses"
if numAnimalsToDispose == 1 then animalTypeText = "horse" end
end
if animalType == AnimalType.CHICKEN and cluster.age < 6 then
animalTypeText = "chicks"
if numAnimalsToDispose == 1 then animalTypeText = "chick" end
elseif animalType == AnimalType.CHICKEN then
animalTypeText = "chickens"
if numAnimalsToDispose == 1 then animalTypeText = "chicken" end
end
msgText = numAnimalsToDispose .. " " .. animalTypeText .. " died due to accidents"
if animalsCanBeSold then msgText = msgText .. ", sold for £" .. math.floor(totalAnimalPrice) end
if numAnimalsToDispose >= 1 then g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText) end
end
end
-- check if given animal pen has a viable male animal for reproduction
function RealisticLivestock.hasMaleAnimalInPen(spec, subT, female)
if spec == nil then return false end
local clusterSystem = spec.clusterSystem or spec
if clusterSystem == nil or clusterSystem.getAnimals == nil or clusterSystem:getAnimals() == nil or female.genetics.fertility <= 0 then return false end
local animals = clusterSystem:getAnimals()
local animalSystem = g_currentMission.animalSystem
local animalType = female == nil and spec.animalTypeIndex or female.animalTypeIndex
local fatherId = (female ~= nil and female.fatherId ~= "-1") and female.fatherId or "-2"
for _, animal in pairs(animals) do
if animal.isCastrated or animal.genetics.fertility <= 0 then continue end
local s = animalSystem:getSubTypeByIndex(animal:getSubTypeIndex())
if s.reproductionMinAgeMonth == nil or s.reproductionMinAgeMonth > animal.age then continue end
if animal:getIdentifiers() == fatherId then continue end
if subT == "COW_WATERBUFFALO" then
if s.name == "BULL_WATERBUFFALO" and animal.age < 132 then return true end
elseif subT == "GOAT" then
if s.name == "RAM_GOAT" and animal.age < 72 then return true end
elseif s.name ~= "RAM_GOAT" and s.name ~= "BULL_WATERBUFFALO" then
if animal.gender == "male" and ((animalType == AnimalType.COW and animal.age < 132) or (animalType == AnimalType.SHEEP and animal.age < 72) or (animalType == AnimalType.HORSE and animal.age < 300) or animalType == AnimalType.CHICKEN or (animalType == AnimalType.PIG and animal.age < 48)) then return true end
end
end
return false
end
-- Monthly Animal Update Call
function RealisticLivestock.onPeriodChanged(self, func)
if self.isServer then
local minTemp = math.floor(g_currentMission.environment.weather.temperatureUpdater.currentMin)
local spec = self.spec_husbandryAnimals
local clusters = spec.clusterSystem:getClusters()
local totalNumAnimals = self:getNumOfAnimals()
local freeSlots = math.max(spec.maxNumAnimals - totalNumAnimals, 0)
local animalSystem = g_currentMission.animalSystem
for _, cluster in ipairs(clusters) do
if cluster.monthsSinceLastBirth == nil then
cluster.monthsSinceLastBirth = 0
end
if cluster.isParent == nil then
cluster.isParent = false
end
cluster:onPeriodChanged()
cluster.monthsSinceLastBirth = cluster.monthsSinceLastBirth + 1
local numNewAnimals = cluster:updateReproduction()
if cluster.monthsSinceLastBirth <= 2 then cluster.reproduction = 0 end
local index = cluster:getSubTypeIndex()
local subTypeFull = animalSystem:getSubTypeByIndex(index)
local reproductionDuration = subTypeFull.reproductionDurationMonth
if cluster.gender == "female" and reproductionDuration ~= nil then
if cluster.reproduction > 0 and cluster.reproduction <= 100 / reproductionDuration and not RealisticLivestock.hasMaleAnimalInPen(spec, subTypeFull.name) then cluster.reproduction = 0 end
end
if numNewAnimals > 0 then
numNewAnimals = math.min(freeSlots, numNewAnimals)
if numNewAnimals > 0 then
RealisticLivestock:updateReproduction(spec, cluster, numNewAnimals, freeSlots, self.isServer)
end
end
RealisticLivestock.CalculateRandomMonthlyAnimalDeaths(spec, cluster, self.isServer)
RealisticLivestock.CalculateOldAgeMonthlyAnimalDeaths(spec, cluster)
RealisticLivestock.CalculateLowHealthMonthlyAnimalDeaths(spec, cluster)
end
spec.minTemp = minTemp
self:raiseActive()
end
end
--PlaceableHusbandryAnimals.onPeriodChanged = Utils.overwrittenFunction(PlaceableHusbandryAnimals.onPeriodChanged, RealisticLivestock.onPeriodChanged)
function RealisticLivestock:updateInfo(superFunc, infoTable)
--superFunc(self, infoTable)
local spec = self.spec_husbandryAnimals
--local health = 0
--local numAnimals = 0
local lactatingAnimals = 0
local clusters = spec.clusterSystem:getClusters()
local numClusters = #clusters
if numClusters > 0 then
for _, cluster in ipairs(clusters) do
--health = health + cluster.health
--numAnimals = numAnimals + cluster.numAnimals
if spec.animalTypeIndex == AnimalType.COW and cluster.isLactating ~= nil and cluster.isLactating then
lactatingAnimals = lactatingAnimals + cluster.numAnimals
end
end
--health = health / numClusters
end
--spec.infoNumAnimals.text = string.format("%d", numAnimals)
--spec.infoHealth.text = string.format("%d %%", health)
--table.insert(infoTable, spec.infoNumAnimals)
--table.insert(infoTable, spec.infoHealth)
local milkSpec = self.spec_husbandryMilk
if spec.animalTypeIndex == AnimalType.COW and milkSpec ~= nil then
if spec.infoLactatingAnimals == nil then
spec.infoLactatingAnimals = {title="Lactating animals", text=""}
end
spec.infoLactatingAnimals.text = string.format("%d", lactatingAnimals)
table.insert(infoTable, spec.infoLactatingAnimals)
end
end
--PlaceableHusbandryAnimals.updateInfo = Utils.appendedFunction(PlaceableHusbandryAnimals.updateInfo, RealisticLivestock.updateInfo)
function RealisticLivestock.addAnimals(self, superFunc, subTypeIndex, numAnimals, age)
local mission = g_currentMission
local animalSystem = mission.animalSystem
local cluster = animalSystem:createClusterFromSubTypeIndex(subTypeIndex)
cluster.gender = animalSystem.subTypes[subTypeIndex].gender
cluster.lactatingAnimals = 0
cluster.monthsSinceLastBirth = 0
cluster.isParent = false
local puberty = animalSystem.subTypes[subTypeIndex].reproductionMinAgeMonth
if puberty ~= nil then
if age >= puberty then
cluster.health = 100
else
cluster.health = (age / puberty) * 100
end
end
if cluster:getSupportsMerging() then
cluster.numAnimals = numAnimals
cluster.age = age
cluster.subTypeIndex = subTypeIndex
self:addCluster(cluster)
else
for i=1, numAnimals do
cluster = animalSystem:createClusterFromSubTypeIndex(subTypeIndex)
cluster.numAnimals = 1
cluster.age = age
self:addCluster(cluster)
end
end
end
--PlaceableHusbandryAnimals.addAnimals = Utils.overwrittenFunction(PlaceableHusbandryAnimals.addAnimals, RealisticLivestock.addAnimals)
-- Saving and Loading
function RealisticLivestock:saveHusbandryToXMLFile(superFunc, xmlFile, key, usedModNames)
superFunc(self, xmlFile, key, usedModNames)
if self.spec_husbandryAnimals.minTemp == nil then self.spec_husbandryAnimals.minTemp = 15 end
xmlFile:setInt(key .. "#minTemp", self.spec_husbandryAnimals.minTemp)
end
PlaceableHusbandryAnimals.saveToXMLFile = Utils.overwrittenFunction(PlaceableHusbandryAnimals.saveToXMLFile, RealisticLivestock.saveHusbandryToXMLFile)
function RealisticLivestock:loadHusbandryFromXMLFile(superFunc, xmlFile, key)
local r = superFunc(self, xmlFile, key)
self.minTemp = xmlFile:getInt(key .. "#minTemp")
if self.minTemp == nil then
self.minTemp = 15
end
return r
end
PlaceableHusbandryAnimals.loadFromXMLFile = Utils.overwrittenFunction(PlaceableHusbandryAnimals.loadFromXMLFile, RealisticLivestock.loadHusbandryFromXMLFile)
================================================
FILE: src/RealisticLivestock_Animal.lua
================================================
Animal = {}
local Animal_mt = Class(Animal)
function Animal.new(age, health, monthsSinceLastBirth, gender, subTypeIndex, reproduction, isParent, isPregnant, isLactating, clusterSystem, id, motherId, fatherId, pos, name, dirt, fitness, riding, farmId, weight, genetics, impregnatedBy, variation, children, monitor, isCastrated, diseases, recentlyBoughtByAI, marks, insemination)
local self = setmetatable({}, Animal_mt)
self.input, self.output = {}, {}
self.isCastrated = isCastrated or false
self.clusterSystem = clusterSystem
self.insemination = insemination
self.recentlyBoughtByAI = false or recentlyBoughtByAI
self.children = children or {}
self.age = age or 0
self.health = health or 0
self.monthsSinceLastBirth = monthsSinceLastBirth or 0
self.gender = gender or "female"
self.subTypeIndex = subTypeIndex or 1
self.subType = g_currentMission.animalSystem:getSubTypeByIndex(self.subTypeIndex).name or "COW_SWISS_BROWN"
self.reproduction = reproduction or 0
self.isParent = isParent or false
self.isPregnant = isPregnant or false
self.isLactating = isLactating or false
self.isDirty = false
self.isIndividual = true
self.name = nil
self.isDead = false
self.isSold = false
self.weight = weight or nil
self.marks = marks or self:getDefaultMarks()
self.variation = variation or nil
self.genetics = genetics
self.impregnatedBy = impregnatedBy
self.animalTypeIndex = g_currentMission.animalSystem:getTypeIndexBySubTypeIndex(self.subTypeIndex)
local subType = g_currentMission.animalSystem:getSubTypeByIndex(self.subTypeIndex)
local targetWeight = subType.targetWeight
self.breed = subType.breed or "UNKNOWN"
if genetics == nil then
self.genetics = {}
local healthChance = math.random()
if healthChance < 0.05 then
self.genetics.health = math.random(25, 35) / 100
elseif healthChance < 0.25 then
self.genetics.health = math.random(35, 90) / 100
elseif healthChance > 0.95 then
self.genetics.health = math.random(165, 175) / 100
elseif healthChance > 0.75 then
self.genetics.health = math.random(110, 165) / 100
else
self.genetics.health = math.random(90, 110) / 100
end
local fertilityChance = math.random()
if fertilityChance < 0.001 then
self.genetics.fertility = 0
elseif fertilityChance < 0.05 then
self.genetics.fertility = math.random(25, 35) / 100
elseif fertilityChance < 0.25 then
self.genetics.fertility = math.random(35, 90) / 100
elseif fertilityChance > 0.95 then
self.genetics.fertility = math.random(165, 175) / 100
elseif fertilityChance > 0.75 then
self.genetics.fertility = math.random(110, 165) / 100
else
self.genetics.fertility = math.random(90, 110) / 100
end
if self.animalTypeIndex == AnimalType.COW or self.animalTypeIndex == AnimalType.SHEEP or self.animalTypeIndex == AnimalType.CHICKEN then
local productivityChance = math.random()
if productivityChance < 0.05 then
self.genetics.productivity = math.random(25, 35) / 100
elseif productivityChance < 0.25 then
self.genetics.productivity = math.random(35, 90) / 100
elseif productivityChance > 0.95 then
self.genetics.productivity = math.random(165, 175) / 100
elseif productivityChance > 0.75 then
self.genetics.productivity = math.random(110, 165) / 100
else
self.genetics.productivity = math.random(90, 110) / 100
end
end
local meatQualityChance = math.random()
if meatQualityChance < 0.05 then
self.genetics.quality = math.random(25, 35) / 100
elseif meatQualityChance < 0.25 then
self.genetics.quality = math.random(35, 90) / 100
elseif meatQualityChance > 0.95 then
self.genetics.quality = math.random(165, 175) / 100
elseif meatQualityChance > 0.75 then
self.genetics.quality = math.random(110, 165) / 100
else
self.genetics.quality = math.random(90, 110) / 100
end
local metabolismChance = math.random()
if metabolismChance < 0.05 then
self.genetics.metabolism = math.random(25, 35) / 100
elseif metabolismChance < 0.25 then
self.genetics.metabolism = math.random(35, 90) / 100
elseif metabolismChance > 0.95 then
self.genetics.metabolism = math.random(165, 175) / 100
elseif metabolismChance > 0.75 then
self.genetics.metabolism = math.random(110, 165) / 100
else
self.genetics.metabolism = math.random(90, 110) / 100
end
end
if self.weight == nil then
local minWeight = subType.minWeight
local maxWeight = subType.maxWeight
local weightPerMonth = (targetWeight - minWeight) / (subType.reproductionMinAgeMonth * 1.5)
self.weight = math.clamp((minWeight + (weightPerMonth * math.clamp(self.age, 0, 20))) * (math.random(85, 115) / 100), minWeight, maxWeight)
end
self.targetWeight = targetWeight + (((targetWeight * self.genetics.metabolism) - targetWeight) / 2.5)
self.farmId = farmId or nil
if self.clusterSystem ~= nil then
if id == nil then
local ownerFarmId = clusterSystem.owner.ownerFarmId
local farm = g_farmManager.farmIdToFarm[ownerFarmId]
if farm == nil then
id = "1"
else
id = farm.stats:getNextAnimalId(g_currentMission.animalSystem:getSubTypeByIndex(self.subTypeIndex).typeIndex)
local farmHerdId = farm.stats.statistics.farmId
if farmHerdId == nil then
farmHerdId = math.random(100000, 999999)
farm.stats.statistics.farmId = farmHerdId
end
self.farmId = tostring(farmHerdId)
id = tostring(id)
local idLen = string.len(id)
if idLen < 5 then
if idLen == 1 then
id = "1000" .. id
elseif idLen == 2 then
id = "100" .. id
elseif idLen == 3 then
id = "10" .. id
elseif idLen == 4 then
id = "1" .. id
end
end
local concatenated = farmHerdId .. id
local checkDigit = (tonumber(concatenated)::number % 7) + 1
id = checkDigit .. id
end
end
if farmId == nil then
local farm = g_farmManager.farmIdToFarm[clusterSystem.owner.ownerFarmId]
if farm == nil then
self.farmId = "1"
else
local farmHerdId = farm.stats.statistics.farmId
if farmHerdId == nil then
farmHerdId = math.random(100000, 999999)
farm.stats.statistics.farmId = farmHerdId
end
self.farmId = tostring(farmHerdId)
end
end
end
self.uniqueId = id
self.id = "0-0"
self.idFull = "0-0"
self.motherId = motherId or "-1"
self.fatherId = fatherId or "-1"
-- for compatibility reasons with mods such as InfoDisplayExtension
self.numAnimals = 1
self.maxNumAnimals = 1
local reproductionText = g_i18n:getText("statistic_reproduction")
self.infoReproduction = {
text = "",
title = reproductionText,
titleOrg = reproductionText
}
self.infoHealth = {
text = "",
title = g_i18n:getText("ui_horseHealth")
}
self.name = name or nil
self.dirt = dirt or 0
self.fitness = fitness or 0
self.riding = riding or 0
if name == "" then name = nil end
self.name = name or ((string.contains(self.subType, "HORSE", true) or string.contains(self.subType, "STALLION", true)) and g_currentMission.animalNameSystem:getRandomName(self.gender) or nil)
self.pos = pos or nil
if self.age >= 0 then
local environment = g_currentMission.environment
local currentMonth = environment.currentPeriod + 2
local currentYear = environment.currentYear
if currentMonth > 12 then currentMonth = currentMonth - 12 end
local birthYear = currentYear - math.floor(self.age / 12)
local birthMonth = currentMonth - (self.age % 12)
if birthMonth <= 0 then birthMonth = 12 + birthMonth end
local birthCountry = math.random() >= 0.01 and RealisticLivestock.getMapCountryIndex() or math.random(1, #RealisticLivestock.AREA_CODES)
self.birthday = {
["day"] = math.random(1, RealisticLivestock.DAYS_PER_MONTH[birthMonth]),
["month"] = birthMonth,
["year"] = birthYear,
["country"] = birthCountry,
["lastAgeMonth"] = currentMonth
}
end
self.diseases = diseases or {}
self:updateInput()
self:updateOutput(g_currentMission.environment.weather.temperatureUpdater.currentMin or 20)
self.monitor = monitor or { ["active"] = false, ["removed"] = false, ["fee"] = 5 }
local animalType = g_currentMission.animalSystem.types[self.animalTypeIndex]
self.monitor.fee = animalType == nil and 5 or math.max(animalType.navMeshAgentAttributes.height * animalType.navMeshAgentAttributes.radius * 15, 0.25)
return self
end
function Animal:delete()
local clusterSystem = self.clusterSystem or nil
if clusterSystem ~= nil then
for i, animal in pairs(clusterSystem.animals) do
if animal == self then
table.remove(clusterSystem.animals, i)
break
end
end
end
self = nil
end
function Animal:setClusterSystem(clusterSystem)
self.clusterSystem = clusterSystem
if clusterSystem ~= nil then self.sale = nil end
end
function Animal:getSupportsMerging()
return false
end
function Animal.loadFromXMLFile(xmlFile, key, clusterSystem, isLegacy)
local subTypeIndex
if isLegacy then
subTypeIndex = xmlFile:getInt(key .. "#subType", 3)
else
local subTypeName = xmlFile:getString(key .. "#subType", "COW_HOLSTEIN")
subTypeIndex = g_currentMission.animalSystem:getSubTypeIndexByName(subTypeName)
end
if subTypeIndex == nil then return nil end
local age = xmlFile:getInt(key .. "#age")
local health = xmlFile:getFloat(key .. "#health")
local monthsSinceLastBirth = xmlFile:getInt(key .. "#monthsSinceLastBirth")
local gender = xmlFile:getString(key .. "#gender")
local reproduction = xmlFile:getFloat(key .. "#reproduction", 0)
local isParent = xmlFile:getBool(key .. "#isParent")
local isPregnant = xmlFile:getBool(key .. "#isPregnant")
local isLactating = xmlFile:getBool(key .. "#isLactating")
local recentlyBoughtByAI = xmlFile:getBool(key .. "#recentlyBoughtByAI", false)
local id = xmlFile:getString(key .. "#id", nil)
local farmId = xmlFile:getString(key .. "#farmId", nil)
local motherId = xmlFile:getString(key .. "#motherId", nil)
local fatherId = xmlFile:getString(key .. "#fatherId", nil)
local weight = xmlFile:getFloat(key .. "#weight", nil)
local variation = xmlFile:getInt(key .. "#variation", nil)
local marks = Animal.getDefaultMarks()
xmlFile:iterate(key .. ".marks.mark", function(_, markKey)
local mark = xmlFile:getString(markKey .. "#key", "PLAYER")
marks[mark].active = xmlFile:getBool(markKey .. "#active", false)
end)
if subTypeIndex == nil then
local subTypeName = xmlFile:getString(key .. "#subType", nil)
if subTypeName == nil then return nil end
subTypeIndex = g_currentMission.animalSystem:getSubTypeIndexByName(subTypeName)
end
local name = xmlFile:getString(key .. "#name", nil)
local dirt = xmlFile:getFloat(key .. "#dirt", nil)
local fitness = xmlFile:getFloat(key .. "#fitness", nil)
local riding = xmlFile:getFloat(key .. "#riding", nil)
local pos = nil
local children = {}
xmlFile:iterate(key .. ".children.child", function (_, childrenKey)
local childUniqueId = xmlFile:getString(childrenKey .. "#uniqueId", nil)
local childFarmId = xmlFile:getString(childrenKey .. "#farmId", nil)
local child = {
farmId = childFarmId,
uniqueId = childUniqueId
}
table.insert(children, child)
end)
local pregnancy
if xmlFile:hasProperty(key .. ".pregnancy") then
pregnancy = { ["pregnancies"] = {} }
local pregnancyKey = key .. ".pregnancy"
pregnancy.expected = {
["day"] = xmlFile:getInt(pregnancyKey .. "#day", 1),
["month"] = xmlFile:getInt(pregnancyKey .. "#month", 1),
["year"] = xmlFile:getInt(pregnancyKey .. "#year", 1)
}
pregnancy.duration = xmlFile:getInt(pregnancyKey .. "#duration", 1)
xmlFile:iterate(pregnancyKey .. ".pregnancies.pregnancy", function (_, pregnanciesKey)
local child = Animal.loadFromXMLFile(xmlFile, pregnanciesKey, nil, isLegacy)
table.insert(pregnancy.pregnancies, child)
end)
end
local birthdayDay = xmlFile:getInt(key .. ".birthday#day", nil)
local birthdayMonth = xmlFile:getInt(key .. ".birthday#month", nil)
local birthdayYear = xmlFile:getInt(key .. ".birthday#year", nil)
local birthdayCountry = xmlFile:getInt(key .. ".birthday#country", nil)
local lastAgeMonth = xmlFile:getInt(key .. ".birthday#lastAgeMonth", 0)
local birthday
if birthdayDay ~= nil and birthdayMonth ~= nil and birthdayYear ~= nil and birthdayCountry ~= nil then
birthday = {
["day"] = birthdayDay,
["month"] = birthdayMonth,
["year"] = birthdayYear,
["country"] = birthdayCountry,
["lastAgeMonth"] = lastAgeMonth
}
end
--local impregnatedById = xmlFile:getString(key .. ".impregnatedBy#uniqueId", nil)
--local impregnatedByMetabolism = xmlFile:getFloat(key .. ".impregnatedBy#metabolism", nil)
--local impregnatedByMeatQuality = xmlFile:getFloat(key .. ".impregnatedBy#quality", nil)
--local impregnatedByProductivity = xmlFile:getFloat(key .. ".impregnatedBy#productivity", nil)
--local impregnatedByHealth = xmlFile:getFloat(key .. ".impregnatedBy#health", nil)
--local impregnatedByFertility = xmlFile:getFloat(key .. ".impregnatedBy#fertility", nil)
local impregnatedBy
if xmlFile:hasProperty(key .. ".impregnatedBy") then
impregnatedBy = {
["uniqueId"] = xmlFile:getString(key .. ".impregnatedBy#uniqueId", nil),
["metabolism"] = xmlFile:getFloat(key .. ".impregnatedBy#metabolism", nil),
["productivity"] = xmlFile:getFloat(key .. ".impregnatedBy#productivity", nil),
["quality"] = xmlFile:getFloat(key .. ".impregnatedBy#quality", nil),
["health"] = xmlFile:getFloat(key .. ".impregnatedBy#health", nil),
["fertility"] = xmlFile:getFloat(key .. ".impregnatedBy#fertility", nil)
}
end
--local metabolism = xmlFile:getFloat(key .. ".genetics#metabolism", nil)
--local productivity = xmlFile:getFloat(key .. ".genetics#productivity", nil)
--local quality = xmlFile:getFloat(key .. ".genetics#quality", nil)
--local healthGenetics = xmlFile:getFloat(key .. ".genetics#health", nil)
--local fertility = xmlFile:getFloat(key .. ".genetics#fertility", nil)
local genetics
if xmlFile:hasProperty(key .. ".genetics") then
genetics = {
["metabolism"] = xmlFile:getFloat(key .. ".genetics#metabolism", nil),
["productivity"] = xmlFile:getFloat(key .. ".genetics#productivity", nil),
["quality"] = xmlFile:getFloat(key .. ".genetics#quality", nil),
["health"] = xmlFile:getFloat(key .. ".genetics#health", nil),
["fertility"] = xmlFile:getFloat(key .. ".genetics#fertility", nil)
}
end
local monitor = { ["active"] = xmlFile:getBool(key .. ".monitor#active", false), ["removed"] = xmlFile:getBool(key .. ".monitor#removed", false) }
local isCastrated = xmlFile:getBool(key .. "#isCastrated", false)
local diseases = {}
xmlFile:iterate(key .. ".diseases.disease", function (_, diseaseKey)
local diseaseType = g_diseaseManager:getDiseaseByTitle(xmlFile:getString(diseaseKey .. "#title"))
local disease = Disease.new(diseaseType)
disease:loadFromXMLFile(xmlFile, diseaseKey)
table.insert(diseases, disease)
end)
local insemination
if xmlFile:hasProperty(key .. ".insemination") then
insemination = {
["country"] = xmlFile:getInt(key .. ".insemination#country"),
["farmId"] = xmlFile:getString(key .. ".insemination#farmId"),
["uniqueId"] = xmlFile:getString(key .. ".insemination#uniqueId"),
["name"] = xmlFile:getString(key .. ".insemination#name"),
["subTypeIndex"] = xmlFile:getInt(key .. ".insemination#subTypeIndex"),
["genetics"] = {},
["success"] = xmlFile:getFloat(key .. ".insemination#success")
}
insemination.genetics.metabolism = xmlFile:getFloat(key .. ".insemination.genetics#metabolism")
insemination.genetics.health = xmlFile:getFloat(key .. ".insemination.genetics#health")
insemination.genetics.fertility = xmlFile:getFloat(key .. ".insemination.genetics#fertility")
insemination.genetics.quality = xmlFile:getFloat(key .. ".insemination.genetics#quality")
insemination.genetics.productivity = xmlFile:getFloat(key .. ".insemination.genetics#productivity")
end
local animal = Animal.new(age, health, monthsSinceLastBirth, gender, subTypeIndex, reproduction, isParent, isPregnant, isLactating, clusterSystem, id, motherId, fatherId, pos, name, dirt, fitness, riding, farmId, weight, genetics, impregnatedBy, variation, children, monitor, isCastrated, diseases, recentlyBoughtByAI, marks, insemination)
--local animal = Animal.new(age, health, monthsSinceLastBirth, gender, subTypeIndex, reproduction, isParent, isPregnant, isLactating, clusterSystem, id, motherId, fatherId, impregnatedById, pos, name, dirt, fitness, riding, farmId, weight, metabolism, impregnatedByMetabolism, impregnatedByProductivity, productivity, quality, impregnatedByMeatQuality, impregnatedByHealth, impregnatedByFertility, healthGenetics, fertility, variation, children)
animal:setBirthday(birthday)
if pregnancy ~= nil and #pregnancy.pregnancies > 0 then
animal.pregnancy = pregnancy
elseif reproduction > 0 then
if animal.clusterSystem ~= nil then
local childNum = animal:generateRandomOffspring()
if childNum > 0 then
local month = g_currentMission.environment.currentPeriod + 2
if month > 12 then month = month - 12 end
local year = g_currentMission.environment.currentYear
animal:createPregnancy(childNum, month, year)
else
animal.reproduction = 0
animal.isPregnant = false
end
else
animal.reproduction = 0
animal.isPregnant = false
end
end
return animal
end
function Animal:saveToXMLFile(xmlFile, key)
xmlFile:setInt(key .. "#age", self.age)
xmlFile:setFloat(key .. "#health", self.health)
xmlFile:setInt(key .. "#monthsSinceLastBirth", self.monthsSinceLastBirth)
xmlFile:setInt(key .. "#numAnimals", 1)
xmlFile:setString(key .. "#gender", self.gender)
xmlFile:setString(key .. "#subType", self.subType)
xmlFile:setFloat(key .. "#reproduction", self.reproduction)
xmlFile:setBool(key .. "#isParent", self.isParent)
xmlFile:setBool(key .. "#isPregnant", self.isPregnant)
xmlFile:setBool(key .. "#isLactating", self.isLactating)
xmlFile:setBool(key .. "#recentlyBoughtByAI", self.recentlyBoughtByAI or false)
xmlFile:setString(key .. "#id", self.uniqueId)
if self.variation ~= nil then xmlFile:setInt(key .. "#variation", self.variation) end
xmlFile:setString(key .. "#farmId", self.farmId)
xmlFile:setString(key .. "#motherId", self.motherId)
xmlFile:setString(key .. "#fatherId", self.fatherId)
xmlFile:setFloat(key .. "#weight", self.weight)
local markI = 0
for _, mark in pairs(self.marks) do
local markKey = string.format("%s.marks.mark(%s)", key, markI)
xmlFile:setString(markKey .. "#key", mark.key)
xmlFile:setBool(markKey .. "#active", mark.active)
markI = markI + 1
end
if self.name ~= nil and self.name ~= "" then xmlFile:setString(key .. "#name", self.name) end
if self.animalTypeIndex == AnimalType.HORSE then
xmlFile:setFloat(key .. "#dirt", self.dirt)
xmlFile:setFloat(key .. "#fitness", self.fitness)
xmlFile:setFloat(key .. "#riding", self.riding)
end
xmlFile:setSortedTable(key .. ".children.child", self.children, function (index, child)
xmlFile:setString(index .. "#uniqueId", child.uniqueId)
xmlFile:setString(index .. "#farmId", child.farmId)
end)
if self.pregnancy ~= nil then
local pregnancy = self.pregnancy
local pregnancyKey = key .. ".pregnancy"
xmlFile:setInt(pregnancyKey .. "#day", pregnancy.expected.day)
xmlFile:setInt(pregnancyKey .. "#month", pregnancy.expected.month)
xmlFile:setInt(pregnancyKey .. "#year", pregnancy.expected.year)
xmlFile:setInt(pregnancyKey .. "#duration", pregnancy.duration)
xmlFile:setSortedTable(pregnancyKey .. ".pregnancies.pregnancy", pregnancy.pregnancies, function (index, child)
xmlFile:setFloat(index .. "#health", child.health)
xmlFile:setString(index .. "#gender", child.gender)
xmlFile:setString(index .. "#subType", child.subType)
xmlFile:setString(index .. "#motherId", child.motherId)
xmlFile:setString(index .. "#fatherId", child.fatherId)
local pregnancyGenetics = child.genetics
if pregnancyGenetics ~= nil then
xmlFile:setFloat(index .. ".genetics#metabolism", pregnancyGenetics.metabolism)
xmlFile:setFloat(index .. ".genetics#quality", pregnancyGenetics.quality)
xmlFile:setFloat(index .. ".genetics#health", pregnancyGenetics.health)
xmlFile:setFloat(index .. ".genetics#fertility", pregnancyGenetics.fertility)
if pregnancyGenetics.productivity ~= nil then xmlFile:setFloat(index .. ".genetics#productivity", pregnancyGenetics.productivity) end
end
xmlFile:setSortedTable(index .. ".diseases.disease", child.diseases, function (diseaseKey, disease)
disease:saveToXMLFile(xmlFile, diseaseKey)
end)
end)
end
if self.impregnatedBy ~= nil then
xmlFile:setString(key .. ".impregnatedBy#uniqueId", self.impregnatedBy.uniqueId)
xmlFile:setFloat(key .. ".impregnatedBy#metabolism", self.impregnatedBy.metabolism)
xmlFile:setFloat(key .. ".impregnatedBy#quality", self.impregnatedBy.quality)
xmlFile:setFloat(key .. ".impregnatedBy#health", self.impregnatedBy.health)
xmlFile:setFloat(key .. ".impregnatedBy#fertility", self.impregnatedBy.fertility)
if self.impregnatedBy.productivity ~= nil then xmlFile:setFloat(key .. ".impregnatedBy#productivity", self.impregnatedBy.productivity) end
end
if self.genetics ~= nil then
xmlFile:setFloat(key .. ".genetics#metabolism", self.genetics.metabolism)
xmlFile:setFloat(key .. ".genetics#quality", self.genetics.quality)
xmlFile:setFloat(key .. ".genetics#health", self.genetics.health)
xmlFile:setFloat(key .. ".genetics#fertility", self.genetics.fertility)
if self.genetics.productivity ~= nil then xmlFile:setFloat(key .. ".genetics#productivity", self.genetics.productivity) end
end
if self.birthday ~= nil then
xmlFile:setInt(key .. ".birthday#day", self.birthday.day)
xmlFile:setInt(key .. ".birthday#month", self.birthday.month)
xmlFile:setInt(key .. ".birthday#year", self.birthday.year)
xmlFile:setInt(key .. ".birthday#country", self.birthday.country)
xmlFile:setInt(key .. ".birthday#lastAgeMonth", self.birthday.lastAgeMonth)
end
if self.insemination ~= nil then
local insemination = self.insemination
xmlFile:setInt(key .. ".insemination#country", insemination.country)
xmlFile:setString(key .. ".insemination#farmId", insemination.farmId)
xmlFile:setString(key .. ".insemination#uniqueId", insemination.uniqueId)
xmlFile:setString(key .. ".insemination#name", insemination.name)
xmlFile:setInt(key .. ".insemination#subTypeIndex", insemination.subTypeIndex)
xmlFile:setFloat(key .. ".insemination#success", insemination.success)
xmlFile:setFloat(key .. ".insemination.genetics#metabolism", insemination.genetics.metabolism)
xmlFile:setFloat(key .. ".insemination.genetics#quality", insemination.genetics.quality)
xmlFile:setFloat(key .. ".insemination.genetics#health", insemination.genetics.health)
xmlFile:setFloat(key .. ".insemination.genetics#fertility", insemination.genetics.fertility)
if insemination.genetics.productivity ~= nil then xmlFile:setFloat(key .. ".insemination.genetics#productivity", insemination.genetics.productivity) end
end
xmlFile:setBool(key .. ".monitor#active", self.monitor.active)
xmlFile:setBool(key .. ".monitor#removed", self.monitor.removed)
if self.isCastrated then xmlFile:setBool(key .. "#isCastrated", true) end
for i, disease in pairs(self.diseases) do
disease:saveToXMLFile(xmlFile, key .. ".diseases.disease(" .. (i - 1) .. ")")
end
end
function Animal:writeStream(streamId, connection)
streamWriteUInt8(streamId, self.subTypeIndex)
streamWriteUInt16(streamId, self.age)
streamWriteFloat32(streamId, self.health)
streamWriteFloat32(streamId, self.reproduction)
streamWriteUInt16(streamId, self.monthsSinceLastBirth)
streamWriteString(streamId, self.gender)
streamWriteBool(streamId, self.isParent)
streamWriteBool(streamId, self.isPregnant and self.pregnancy ~= nil)
streamWriteBool(streamId, self.isLactating)
streamWriteBool(streamId, self.recentlyBoughtByAI or false)
local numMarks = 0
for key, mark in pairs(self.marks) do numMarks = numMarks + 1 end
streamWriteUInt8(streamId, numMarks)
for key, mark in pairs(self.marks) do
streamWriteString(streamId, key)
streamWriteBool(streamId, mark.active)
end
streamWriteString(streamId, self.uniqueId)
streamWriteString(streamId, self.farmId)
streamWriteUInt8(streamId, self.variation or 1)
streamWriteString(streamId, self.motherId or "-1")
streamWriteString(streamId, self.fatherId or "-1")
streamWriteFloat32(streamId, self.weight)
streamWriteFloat32(streamId, self.targetWeight)
streamWriteBool(streamId, self.name ~= nil and self.name ~= "")
if self.name ~= nil and self.name ~= "" then streamWriteString(streamId, self.name) end
streamWriteFloat32(streamId, self.dirt or 0)
streamWriteFloat32(streamId, self.fitness or 0)
streamWriteFloat32(streamId, self.riding or 0)
if self.isPregnant and self.pregnancy ~= nil then
streamWriteBool(streamId, self.impregnatedBy ~= nil)
if self.impregnatedBy ~= nil then
local impregnatedBy = self.impregnatedBy
streamWriteString(streamId, impregnatedBy.uniqueId or "-1")
streamWriteFloat32(streamId, impregnatedBy.metabolism or 1)
streamWriteFloat32(streamId, impregnatedBy.productivity or 1)
streamWriteFloat32(streamId, impregnatedBy.quality or 1)
streamWriteFloat32(streamId, impregnatedBy.health or 1)
streamWriteFloat32(streamId, impregnatedBy.fertility or 1)
end
local pregnancy = self.pregnancy
streamWriteUInt8(streamId, pregnancy.expected.day)
streamWriteUInt8(streamId, pregnancy.expected.month)
streamWriteUInt8(streamId, pregnancy.expected.year)
streamWriteUInt8(streamId, pregnancy.duration)
streamWriteUInt8(streamId, pregnancy.pregnancies == nil and 0 or #pregnancy.pregnancies)
for _, child in pairs(pregnancy.pregnancies or {}) do
streamWriteFloat32(streamId, child.health)
streamWriteString(streamId, child.gender)
streamWriteUInt8(streamId, child.subTypeIndex)
streamWriteString(streamId, child.motherId)
streamWriteString(streamId, child.fatherId)
local genetics = child.genetics
streamWriteFloat32(streamId, genetics.metabolism)
streamWriteFloat32(streamId, genetics.health)
streamWriteFloat32(streamId, genetics.fertility)
streamWriteFloat32(streamId, genetics.quality)
streamWriteFloat32(streamId, genetics.productivity or 0)
end
end
if self.isParent then
streamWriteUInt16(streamId, #self.children)
for _, child in pairs(self.children or {}) do
streamWriteString(streamId, child.uniqueId or "")
streamWriteString(streamId, child.farmId or "")
end
end
local birthday = self.birthday
streamWriteUInt8(streamId, birthday.day)
streamWriteUInt8(streamId, birthday.month)
streamWriteUInt8(streamId, birthday.year)
streamWriteUInt8(streamId, birthday.country)
streamWriteUInt8(streamId, birthday.lastAgeMonth)
local genetics, numGenetics = self.genetics, 0
for trait, quality in pairs(genetics) do numGenetics = numGenetics + 1 end
streamWriteUInt8(streamId, numGenetics)
for trait, quality in pairs(genetics) do
streamWriteString(streamId, trait)
streamWriteFloat32(streamId, quality)
end
streamWriteBool(streamId, self.monitor.active)
streamWriteBool(streamId, self.monitor.removed)
streamWriteFloat32(streamId, self.monitor.fee or 5)
streamWriteBool(streamId, self.isCastrated or false)
streamWriteUInt8(streamId, #self.diseases)
for i = 1, #self.diseases do
self.diseases[i]:writeStream(streamId, connection)
end
streamWriteBool(streamId, self.insemination ~= nil)
if self.insemination ~= nil then
streamWriteUInt8(streamId, self.insemination.country)
streamWriteString(streamId, self.insemination.farmId)
streamWriteString(streamId, self.insemination.uniqueId)
streamWriteString(streamId, self.insemination.name)
streamWriteUInt8(streamId, self.insemination.subTypeIndex)
streamWriteFloat32(streamId, self.insemination.success)
streamWriteFloat32(streamId, self.insemination.genetics.metabolism)
streamWriteFloat32(streamId, self.insemination.genetics.health)
streamWriteFloat32(streamId, self.insemination.genetics.fertility)
streamWriteFloat32(streamId, self.insemination.genetics.quality)
streamWriteFloat32(streamId, self.insemination.genetics.productivity or 0)
end
return true
end
function Animal:readStream(streamId, connection)
self.subTypeIndex = streamReadUInt8(streamId)
self.subType = g_currentMission.animalSystem:getSubTypeByIndex(self.subTypeIndex).name
self.animalTypeIndex = g_currentMission.animalSystem:getTypeIndexBySubTypeIndex(self.subTypeIndex)
self.age = streamReadUInt16(streamId)
self.health = streamReadFloat32(streamId)
self.reproduction = streamReadFloat32(streamId)
self.monthsSinceLastBirth = streamReadUInt16(streamId)
self.gender = streamReadString(streamId)
self.isParent = streamReadBool(streamId)
self.isPregnant = streamReadBool(streamId)
self.isLactating = streamReadBool(streamId)
self.recentlyBoughtByAI = streamReadBool(streamId)
local numMarks = streamReadUInt8(streamId)
for i = 1, numMarks do
local key = streamReadString(streamId)
local active = streamReadBool(streamId)
self.marks[key].active = active
end
self.uniqueId = streamReadString(streamId)
self.farmId = streamReadString(streamId)
self.variation = streamReadUInt8(streamId)
self.motherId = streamReadString(streamId)
self.fatherId = streamReadString(streamId)
self.weight = streamReadFloat32(streamId)
self.targetWeight = streamReadFloat32(streamId)
local hasName = streamReadBool(streamId)
self.name = hasName and streamReadString(streamId) or nil
self.dirt = streamReadFloat32(streamId)
self.fitness = streamReadFloat32(streamId)
self.riding = streamReadFloat32(streamId)
if self.isPregnant then
if streamReadBool(streamId) then
local uniqueId = streamReadString(streamId)
local metabolism = streamReadFloat32(streamId)
local productivity = streamReadFloat32(streamId)
local quality = streamReadFloat32(streamId)
local health = streamReadFloat32(streamId)
local fertility = streamReadFloat32(streamId)
self.impregnatedBy = {
["uniqueId"] = uniqueId,
["metabolism"] = metabolism,
["productivity"] = productivity,
["quality"] = quality,
["health"] = health,
["fertility"] = fertility
}
end
local pregnancy = { ["expected"] = {}, ["pregnancies"] = {} }
pregnancy.expected.day = streamReadUInt8(streamId)
pregnancy.expected.month = streamReadUInt8(streamId)
pregnancy.expected.year = streamReadUInt8(streamId)
pregnancy.duration = streamReadUInt8(streamId)
local numChildren = streamReadUInt8(streamId)
for i = 1, numChildren do
local health = streamReadFloat32(streamId)
local gender = streamReadString(streamId)
local subTypeIndex = streamReadUInt8(streamId)
local motherId = streamReadString(streamId)
local fatherId = streamReadString(streamId)
local genetics = {}
genetics.metabolism = streamReadFloat32(streamId)
genetics.health = streamReadFloat32(streamId)
genetics.fertility = streamReadFloat32(streamId)
genetics.quality = streamReadFloat32(streamId)
local productivity = streamReadFloat32(streamId)
if productivity ~= nil then genetics.productivity = productivity end
local child = Animal.new(0, health, 0, gender, subTypeIndex, 0, false, false, false, nil, nil, motherId, fatherId, nil, nil, nil, nil, nil, nil, nil, genetics)
table.insert(pregnancy.pregnancies, child)
end
self.pregnancy = pregnancy
end
if self.isParent then
local children = {}
local numChildren = streamReadUInt16(streamId)
for i = 1, numChildren do
table.insert(children, {
["uniqueId"] = streamReadString(streamId),
["farmId"] = streamReadString(streamId)
})
end
self.children = children
end
self.birthday = {
["day"] = streamReadUInt8(streamId),
["month"] = streamReadUInt8(streamId),
["year"] = streamReadUInt8(streamId),
["country"] = streamReadUInt8(streamId),
["lastAgeMonth"] = streamReadUInt8(streamId)
}
self.genetics = {}
local numGenetics = streamReadUInt8(streamId)
for i = 1, numGenetics do
local trait = streamReadString(streamId)
local quality = streamReadFloat32(streamId)
self.genetics[trait] = quality
end
self.monitor = {
["active"] = streamReadBool(streamId),
["removed"] = streamReadBool(streamId),
["fee"] = streamReadFloat32(streamId)
}
self.isCastrated = streamReadBool(streamId)
local numDiseases = streamReadUInt8(streamId)
local diseases = {}
for i = 1, numDiseases do
local diseaseType = g_diseaseManager:getDiseaseByTitle(streamReadString(streamId))
local disease = Disease.new(diseaseType)
disease:readStream(streamId, connection)
table.insert(diseases, disease)
end
self.diseases = diseases
local hasInsemination = streamReadBool(streamId)
local insemination
if hasInsemination then
insemination = {
["country"] = streamReadUInt8(streamId),
["farmId"] = streamReadString(streamId),
["uniqueId"] = streamReadString(streamId),
["name"] = streamReadString(streamId),
["subTypeIndex"] = streamReadUInt8(streamId),
["genetics"] = {},
["success"] =streamReadFloat32(streamId)
}
insemination.genetics.metabolism = streamReadFloat32(streamId)
insemination.genetics.health = streamReadFloat32(streamId)
insemination.genetics.fertility = streamReadFloat32(streamId)
insemination.genetics.quality = streamReadFloat32(streamId)
insemination.genetics.productivity = streamReadFloat32(streamId)
if insemination.genetics.productivity == 0 then insemination.genetics.productivity = nil end
end
self.insemination = insemination
return true
end
function Animal:writeStreamIdentifiers(streamId, connection)
streamWriteString(streamId, self.uniqueId)
streamWriteString(streamId, self.farmId)
streamWriteUInt8(streamId, self.birthday.country)
streamWriteUInt8(streamId, self.animalTypeIndex)
return true
end
function Animal.readStreamIdentifiers(streamId, connection)
local uniqueId = streamReadString(streamId)
local farmId = streamReadString(streamId)
local country = streamReadUInt8(streamId)
local animalTypeIndex = streamReadUInt8(streamId)
return {
["uniqueId"] = uniqueId,
["farmId"] = farmId,
["country"] = country,
["animalTypeIndex"] = animalTypeIndex
}
end
function Animal:writeStreamUnborn(streamId, connection)
streamWriteUInt8(streamId, self.subTypeIndex)
streamWriteFloat32(streamId, self.health)
streamWriteString(streamId, self.gender)
streamWriteString(streamId, self.motherId or "-1")
streamWriteString(streamId, self.fatherId or "-1")
streamWriteFloat32(streamId, self.targetWeight)
local genetics, numGenetics = self.genetics, 0
for trait, quality in pairs(genetics) do numGenetics = numGenetics + 1 end
streamWriteUInt8(streamId, numGenetics)
for trait, quality in pairs(genetics) do
streamWriteString(streamId, trait)
streamWriteFloat32(streamId, quality)
end
streamWriteUInt8(streamId, #self.diseases)
for i = 1, #self.diseases do
self.diseases[i]:writeStream(streamId, connection)
end
return true
end
function Animal:readStreamUnborn(streamId, connection)
self.subTypeIndex = streamReadUInt8(streamId)
self.subType = g_currentMission.animalSystem:getSubTypeByIndex(self.subTypeIndex).name
self.animalTypeIndex = g_currentMission.animalSystem:getTypeIndexBySubTypeIndex(self.subTypeIndex)
self.health = streamReadFloat32(streamId)
self.gender = streamReadString(streamId)
self.motherId = streamReadString(streamId)
self.fatherId = streamReadString(streamId)
self.targetWeight = streamReadFloat32(streamId)
self.genetics = {}
local numGenetics = streamReadUInt8(streamId)
for i = 1, numGenetics do
local trait = streamReadString(streamId)
local quality = streamReadFloat32(streamId)
self.genetics[trait] = quality
end
local numDiseases = streamReadUInt8(streamId)
local diseases = {}
for i = 1, numDiseases do
local diseaseType = g_diseaseManager:getDiseaseByTitle(streamReadString(streamId))
local disease = Disease.new(diseaseType)
disease:readStream(streamId, connection)
table.insert(diseases, disease)
end
self.diseases = diseases
return true
end
function Animal:clone()
local impregnatedBy = self.impregnatedBy or nil
--local newAnimal = self.new(self.age, self.health, self.monthsSinceLastBirth, self.gender, self.subTypeIndex, self.reproduction, self.isParent, self.isPregnant, self.isLactating, self.clusterSystem, self.uniqueId, self.motherId, self.fatherId, impregnatedBy ~= nil and impregnatedBy.uniqueId or nil, self.pos or nil, self.name or nil, self.dirt or nil, self.fitness or nil, self.riding or nil, self.farmId, self.weight, self.metabolism, impregnatedBy ~= nil and impregnatedBy.metabolism or nil, impregnatedBy ~= nil and impregnatedBy.productivity or nil, self.genetics.productivity or nil, self.genetics.quality, impregnatedBy ~= nil and impregnatedBy.quality or nil, impregnatedBy ~= nil and impregnatedBy.health or nil, impregnatedBy ~= nil and impregnatedBy.fertility or nil, self.genetics.health, self.genetics.fertility, self.variation, self.children)
local newAnimal = self.new(self.age, self.health, self.monthsSinceLastBirth, self.gender, self.subTypeIndex, self.reproduction, self.isParent, self.isPregnant, self.isLactating, self.clusterSystem, self.uniqueId, self.motherId, self.fatherId, self.pos, self.name, self.dirt, self.fitness, self.riding, self.farmId, self.weight, self.genetics, self.impregnatedBy, self.variation, self.children, self.monitor, self.isCastrated, self.diseases, self.recentlyBoughtByAI, self.marks, self.insemination)
--if self.impregnatedBy ~= nil then
--newAnimal.impregnatedBy = {
--uniqueId = self.impregnatedBy.uniqueId,
--metabolism = self.impregnatedBy.metabolism,
--quality = self.impregnatedBy.quality,
--health = self.impregnatedBy.health,
--fertility = self.impregnatedBy.fertility
--}
--end
newAnimal:setBirthday(self.birthday)
if self.pregnancy ~= nil then newAnimal.pregnancy = self.pregnancy end
return newAnimal
end
function Animal:setBirthday(birthday)
if birthday ~= nil then self.birthday = birthday end
end
function Animal:getBirthday()
return self.birthday
end
function Animal:setGenetics(genetics)
self.genetics = genetics
end
function Animal:getGenetics()
return self.genetics
end
function Animal:setUniqueId(farmId)
if self.clusterSystem == nil then
if farmId == nil then return end
if type(farmId) == "string" then farmId = tonumber(farmId) end
local id = g_currentMission.animalSystem:getNextAnimalIdForFarm(self.birthday.country, self.animalTypeIndex, farmId)
id = tostring(id)
local idLen = string.len(id)
if idLen < 5 then
if idLen == 1 then
id = "1000" .. id
elseif idLen == 2 then
id = "100" .. id
elseif idLen == 3 then
id = "10" .. id
elseif idLen == 4 then
id = "1" .. id
end
end
local concatenated = farmId .. id
local checkDigit = (tonumber(concatenated)::number % 7) + 1
id = checkDigit .. id
self.farmId = tostring(farmId)
self.uniqueId = id
return
end
local ownerFarmId = self.clusterSystem.owner.ownerFarmId
if ownerFarmId == nil then
self.uniqueId, self.farmId = "1", "1"
return
end
local farm = g_farmManager.farmIdToFarm[ownerFarmId]
if farm == nil then
self.uniqueId, self.farmId = "1", "1"
else
id = farm.stats:getNextAnimalId(g_currentMission.animalSystem:getSubTypeByIndex(self.subTypeIndex).typeIndex)
local farmHerdId = farm.stats.statistics.farmId
if farmHerdId == nil then
farmHerdId = math.random(100000, 999999)
farm.stats.statistics.farmId = farmHerdId
end
id = tostring(id)
local idLen = string.len(id)
if idLen < 5 then
if idLen == 1 then
id = "1000" .. id
elseif idLen == 2 then
id = "100" .. id
elseif idLen == 3 then
id = "10" .. id
elseif idLen == 4 then
id = "1" .. id
end
end
local concatenated = farmHerdId .. id
local checkDigit = (tonumber(concatenated)::number % 7) + 1
id = checkDigit .. id
self.farmId = tostring(farmHerdId)
self.uniqueId = id
end
end
function Animal:getHash()
return (100 + self.age) + (1000 * (100 + self.health)) + (1000000 * (100 + self.reproduction)) + (1000000000 * (100 + self.subTypeIndex))
end
function Animal:changeNumAnimals(delta)
local oldNum = self.numAnimals
self.numAnimals = math.clamp(math.floor(self.numAnimals + delta), 0, 1)
self:setDirty()
return delta - (self.numAnimals - oldNum)
end
function Animal:setDirty()
self.isDirty = true
if self.clusterSystem ~= nil then self.clusterSystem:setDirty() end
end
function Animal:getRidableFilename()
return self:getSubType().rideableFilename or nil
end
function Animal:getNumAnimals()
return self.numAnimals
end
function Animal:getSubTypeIndex()
return self.subTypeIndex
end
function Animal:getSubType()
return g_currentMission.animalSystem:getSubTypeByName(self.subType)
end
function Animal:increaseAge()
self.age = self.age + 1
end
function Animal:getAge()
return self.age
end
function Animal:getName()
return self.name or ""
end
function Animal:setName(name)
self.name = name
end
function Animal:getTranportationFee(factor)
return g_currentMission.animalSystem:getAnimalTransportFee(self.subTypeIndex, self.age) * factor
end
function Animal:getCanBeSold()
return self.isDead == false
end
function Animal:addInfos(infos)
local subType = self:getSubType()
local hasMonitor = self.monitor.active or self.monitor.removed
local healthFactor = self:getHealthFactor()
if hasMonitor then
self.infoHealth.value = healthFactor
self.infoHealth.ratio = healthFactor
self.infoHealth.valueText = string.format("%d %%", g_i18n:formatNumber(healthFactor * 100, 0))
table.insert(infos, self.infoHealth)
end
if self:getSupportsReproduction() then
local reproductionFactor = self:getReproductionFactor()
self.infoReproduction.value = reproductionFactor
self.infoReproduction.ratio = reproductionFactor
self.infoReproduction.valueText = string.format("%d %%", g_i18n:formatNumber(reproductionFactor * 100, 0))
self.infoReproduction.disabled = not self:getCanReproduce()
self.infoReproduction.title = self.infoReproduction.titleOrg
if self.infoReproduction.disabled then
local attributeText, valueText = nil
if self.age < subType.reproductionMinAgeMonth then
attributeText = g_i18n:getText("rl_ui_tooYoung")
valueText = g_i18n:formatNumMonth(subType.reproductionMinAgeMonth)
elseif self.isParent and self.monthsSinceLastBirth <= 2 then
attributeText = g_i18n:getText("rl_ui_recoveringLastBirth")
valueText = g_i18n:formatNumMonth(3 - self.monthsSinceLastBirth)
elseif not RealisticLivestock.hasMaleAnimalInPen(self.clusterSystem, subType.name, self) and self.reproduction == 0 then
attributeText = g_i18n:getText("rl_ui_noMaleAnimal")
valueText = "0"
elseif healthFactor < subType.reproductionMinHealth then
attributeText = g_i18n:getText("rl_ui_unhealthy")
valueText = string.format("%d %%", subType.reproductionMinHealth)
end
self.infoReproduction.title = self.infoReproduction.title .. string.format(" (%s < %s)", attributeText, valueText)
end
table.insert(infos, self.infoReproduction)
end
if hasMonitor then
if self.infoWeight == nil then
self.infoWeight = {
text = g_i18n:getText("rl_ui_weight"),
title = g_i18n:getText("rl_ui_weight")
}
end
self.infoWeight.value = 1
self.infoWeight.ratio = self.weight / self.targetWeight
self.infoWeight.valueText = string.format("%.2f", self.weight) .. "kg / " .. string.format("%.2f", self.targetWeight) .. "kg"
table.insert(infos, self.infoWeight)
end
if self.gender ~= nil and self.gender == "female" then
if self.infoPregnant == nil then
self.infoPregnant = {
text = g_i18n:getText("rl_ui_pregnant"),
title = g_i18n:getText("rl_ui_pregnant")
}
end
self.infoPregnant.value = 1
self.infoPregnant.ratio = self.isPregnant and 1 or 0
self.infoPregnant.valueText = self.isPregnant and g_i18n:getText("rl_ui_yes") or g_i18n:getText("rl_ui_no")
table.insert(infos, self.infoPregnant)
local pregnancy = self.pregnancy
if pregnancy ~= nil and pregnancy.pregnancies and #pregnancy.pregnancies > 0 then
if self.infoPregnancyExpecting == nil then
self.infoPregnancyExpecting = {
text = g_i18n:getText("rl_ui_pregnancyExpecting"),
title = g_i18n:getText("rl_ui_pregnancyExpecting"),
value = 1,
ratio = 1
}
end
if self.infoPregnancyExpected == nil then
self.infoPregnancyExpected = {
text = g_i18n:getText("rl_ui_pregnancyExpected"),
title = g_i18n:getText("rl_ui_pregnancyExpected"),
value = 1,
ratio = 1
}
end
self.infoPregnancyExpecting.valueText = string.format("%s %s", #pregnancy.pregnancies, g_i18n:getText("rl_ui_pregnancy" .. (#pregnancy.pregnancies == 1 and "Baby" or "Babies")))
self.infoPregnancyExpected.valueText = string.format("%s/%s/%s", pregnancy.expected.day, pregnancy.expected.month, pregnancy.expected.year + RealisticLivestock.START_YEAR.FULL)
table.insert(infos, self.infoPregnancyExpecting)
table.insert(infos, self.infoPregnancyExpected)
end
if self.isLactating ~= nil and hasMonitor and self.age > 12 and self.clusterSystem ~= nil and self.clusterSystem.owner.spec_husbandryMilk ~= nil then
if self.infoLactation == nil then
self.infoLactation = {
text = g_i18n:getText("rl_ui_lactating"),
title = g_i18n:getText("rl_ui_lactating")
}
end
self.infoLactation.value = 1
self.infoLactation.ratio = self.isLactating and 1 or 0
self.infoLactation.valueText = self.isLactating and g_i18n:getText("rl_ui_yes") or g_i18n:getText("rl_ui_no")
table.insert(infos, self.infoLactation)
end
end
if self.animalTypeIndex == AnimalType.HORSE then
if self.infoFitness == nil then
self.infoFitness = {
text = "",
title = g_i18n:getText("ui_horseFitness")
}
end
local fitness = self:getFitnessFactor()
self.infoFitness.value = fitness
self.infoFitness.ratio = fitness
self.infoFitness.valueText = string.format("%d %%", g_i18n:formatNumber(fitness * 100, 0))
table.insert(infos, self.infoFitness)
if self.infoRiding == nil then
self.infoRiding = {
text = "",
title = g_i18n:getText("ui_horseDailyRiding")
}
end
local riding = self:getRidingFactor()
self.infoRiding.value = riding
self.infoRiding.ratio = riding
self.infoRiding.valueText = string.format("%d %%", g_i18n:formatNumber(riding * 100, 0))
table.insert(infos, self.infoRiding)
if Platform.gameplay.needHorseCleaning then
if self.infoCleanliness == nil then
self.infoCleanliness = {
text = "",
title = g_i18n:getText("statistic_cleanliness")
}
end
local cleanliness = 1 - self:getDirtFactor()
self.infoCleanliness.value = cleanliness
self.infoCleanliness.ratio = cleanliness
self.infoCleanliness.valueText = string.format("%d %%", g_i18n:formatNumber(cleanliness * 100, 0))
table.insert(infos, self.infoCleanliness)
end
end
end
function Animal:showInfo(box)
local index = self:getSubTypeIndex()
local subType = self:getSubType()
local name = subType.name
local yesText = g_i18n:getText("rl_ui_yes")
local noText = g_i18n:getText("rl_ui_no")
local fillTypeTitle = g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)
box:addLine(g_i18n:getText("infohud_type"), fillTypeTitle)
if self:getName() ~= "" then box:addLine(g_i18n:getText("infohud_name"), self:getName()) end
box:addLine(g_i18n:getText("rl_ui_uniqueId"), self.uniqueId)
box:addLine(g_i18n:getText("rl_ui_farmId"), self.farmId)
box:addLine(g_i18n:getText("infohud_age"), RealisticLivestock.formatAge(self.age))
if self.birthday ~= nil then
local birthday = self.birthday
box:addLine(g_i18n:getText("rl_ui_birthday"), string.format("%d/%d/%d", birthday.day, birthday.month, RealisticLivestock.START_YEAR.FULL + birthday.year))
end
box:addLine(g_i18n:getText("rl_ui_gender"), self.gender == "male" and g_i18n:getText("rl_ui_male") or g_i18n:getText("rl_ui_female"))
if string.contains(self.subType, "HORSE", true) or string.contains(self.subType, "STALLION", true) then
box:addLine(g_i18n:getText("infohud_riding"), string.format("%d%%", self.riding))
box:addLine(g_i18n:getText("infohud_fitness"), string.format("%d%%", self.fitness))
if Platform.gameplay.needHorseCleaning then box:addLine(g_i18n:getText("statistic_cleanliness"), string.format("%d%%", 100 - self.dirt)) end
end
if self.gender ~= nil and self.gender == "female" and subType.supportsReproduction then
box:addLine(g_i18n:getText("infohud_reproduction"), string.format("%d%%", self.reproduction))
local pregnancy = self.pregnancy
if pregnancy ~= nil and pregnancy.pregnancies and #pregnancy.pregnancies > 0 then
box:addLine(g_i18n:getText("rl_ui_pregnancyExpecting"), string.format("%s %s", #pregnancy.pregnancies, g_i18n:getText("rl_ui_pregnancy" .. (#pregnancy.pregnancies == 1 and "Baby" or "Babies"))))
box:addLine(g_i18n:getText("rl_ui_pregnancyExpected"), string.format("%s/%s/%s", pregnancy.expected.day, pregnancy.expected.month, pregnancy.expected.year + RealisticLivestock.START_YEAR.FULL))
end
local healthFactor = self:getHealthFactor()
local text = yesText
if self.age < subType.reproductionMinAgeMonth then
text = g_i18n:getText("rl_ui_tooYoungBracketed")
elseif self.isParent and self.monthsSinceLastBirth <= 2 then
text = g_i18n:getText("rl_ui_recoveringLastBirthBracketed")
elseif self.clusterSystem ~= nil and not RealisticLivestock.hasMaleAnimalInPen(self.clusterSystem.owner.spec_husbandryAnimals, name, self) and not self.isPregnant then
text = g_i18n:getText("rl_ui_noMaleAnimalBracketed")
elseif healthFactor < subType.reproductionMinHealth then
text = g_i18n:getText("rl_ui_unhealthyBracketed")
end
box:addLine(g_i18n:getText("rl_ui_canReproduce"), text)
if self.age >= subType.reproductionMinAgeMonth then box:addLine(g_i18n:getText("rl_ui_pregnant"), self.isPregnant and yesText or noText) end
if self.isPregnant then box:addLine(g_i18n:getText("rl_ui_impregnatedBy"), (self.impregnatedBy ~= nil and self.impregnatedBy.uniqueId ~= "-1") and self.impregnatedBy.uniqueId or g_i18n:getText("rl_ui_unknown")) end
elseif self.gender ~= nil and self.gender == "male" and subType.reproductionMinAgeMonth ~= nil and self.age >= subType.reproductionMinAgeMonth then
local monotonicHour = g_currentMission.environment:getMonotonicHour()
if self.numImpregnatableAnimals == nil or (self.lastNumImpregnatableAnimalsUpdate ~= nil and monotonicHour >= self.lastNumImpregnatableAnimalsUpdate + 1) then
self.lastNumImpregnatableAnimalsUpdate = monotonicHour
self.numImpregnatableAnimals = self:getNumberOfImpregnatableFemalesForMale()
end
box:addLine(g_i18n:getText("rl_ui_maleNumImpregnatable"), string.format("%s", self.numImpregnatableAnimals or 0))
end
box:addLine(g_i18n:getText("rl_ui_value"), g_i18n:formatMoney(self:getSellPrice(), 2, true, true))
if self.isCastrated then box:addLine(g_i18n:getText("rl_ui_castrated"), g_i18n:getText("rl_ui_yes")) end
end
function Animal:showGeneticsInfo(box)
local genetics = self.genetics
local metabolism = genetics.metabolism
local typeIndex = self.animalTypeIndex
local overallGenetics = metabolism + genetics.quality + genetics.health + genetics.fertility + (genetics.productivity ~= nil and genetics.productivity or 0)
local bestGenetics = 1.75 + 1.75 + 1.75 + 1.75 + (genetics.productivity ~= nil and 1.75 or 0)
local qualityText = "extremelyBad"
local geneticsFactor = overallGenetics / bestGenetics
if geneticsFactor >= 0.95 then
qualityText = "extremelyGood"
elseif geneticsFactor >= 0.8 then
qualityText = "veryGood"
elseif geneticsFactor >= 0.65 then
qualityText = "good"
elseif geneticsFactor >= 0.35 then
qualityText = "average"
elseif geneticsFactor >= 0.2 then
qualityText = "bad"
elseif geneticsFactor >= 0.05 then
qualityText = "veryBad"
end
box:addLine("rl_ui_overall", "rl_ui_genetics_" .. qualityText)
if metabolism >= 1.65 then
qualityText = "extremelyHigh"
elseif metabolism >= 1.4 then
qualityText = "veryHigh"
elseif metabolism >= 1.1 then
qualityText = "high"
elseif metabolism >= 0.9 then
qualityText = "average"
elseif metabolism >= 0.7 then
qualityText = "low"
elseif metabolism >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
box:addLine(g_i18n:getText("rl_ui_metabolism"), "rl_ui_genetics_" .. qualityText)
local health = genetics.health
if health >= 1.65 then
qualityText = "extremelyHigh"
elseif health >= 1.4 then
qualityText = "veryHigh"
elseif health >= 1.1 then
qualityText = "high"
elseif health >= 0.9 then
qualityText = "average"
elseif health >= 0.7 then
qualityText = "low"
elseif health >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
box:addLine(g_i18n:getText("rl_ui_health"), "rl_ui_genetics_" .. qualityText)
local fertility = genetics.fertility
if fertility >= 1.65 then
qualityText = "extremelyHigh"
elseif fertility >= 1.4 then
qualityText = "veryHigh"
elseif fertility >= 1.1 then
qualityText = "high"
elseif fertility >= 0.9 then
qualityText = "average"
elseif fertility >= 0.7 then
qualityText = "low"
elseif fertility >= 0.35 then
qualityText = "veryLow"
elseif fertility > 0 then
qualityText = "extremelyLow"
else
qualityText = "infertile"
end
box:addLine(g_i18n:getText("rl_ui_fertility"), "rl_ui_genetics_" .. qualityText)
local meat = genetics.quality
if meat >= 1.65 then
qualityText = "extremelyHigh"
elseif meat >= 1.4 then
qualityText = "veryHigh"
elseif meat >= 1.1 then
qualityText = "high"
elseif meat >= 0.9 then
qualityText = "average"
elseif meat >= 0.7 then
qualityText = "low"
elseif meat >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
box:addLine(g_i18n:getText("rl_ui_meat"), "rl_ui_genetics_" .. qualityText)
if genetics.productivity ~= nil then
local productivity = genetics.productivity
if productivity >= 1.65 then
qualityText = "extremelyHigh"
elseif productivity >= 1.4 then
qualityText = "veryHigh"
elseif productivity >= 1.1 then
qualityText = "high"
elseif productivity >= 0.9 then
qualityText = "average"
elseif productivity >= 0.7 then
qualityText = "low"
elseif productivity >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
if typeIndex == AnimalType.COW then box:addLine(g_i18n:getText("rl_ui_milk"), "rl_ui_genetics_" .. qualityText) end
if typeIndex == AnimalType.SHEEP then box:addLine(g_i18n:getText("rl_ui_wool"), "rl_ui_genetics_" .. qualityText) end
if typeIndex == AnimalType.CHICKEN then box:addLine(g_i18n:getText("rl_ui_eggs"), "rl_ui_genetics_" .. qualityText) end
end
end
function Animal:addGeneticsInfo()
local texts = {}
local genetics = self.genetics
if genetics == nil then return {} end
local text = {}
local metabolism = genetics.metabolism
local overallGenetics = metabolism + genetics.quality + genetics.health + genetics.fertility + (genetics.productivity ~= nil and genetics.productivity or 0)
local bestGenetics = 1.75 + 1.75 + 1.75 + 1.75 + (genetics.productivity ~= nil and 1.75 or 0)
local qualityText = "extremelyBad"
local geneticsFactor = overallGenetics / bestGenetics
if geneticsFactor >= 0.95 then
qualityText = "extremelyGood"
elseif geneticsFactor >= 0.8 then
qualityText = "veryGood"
elseif geneticsFactor >= 0.6 then
qualityText = "good"
elseif geneticsFactor >= 0.4 then
qualityText = "average"
elseif geneticsFactor >= 0.2 then
qualityText = "bad"
elseif geneticsFactor >= 0.05 then
qualityText = "veryBad"
end
text = {
title = g_i18n:getText("rl_ui_overall"),
text = "rl_ui_genetics_" .. qualityText
}
table.insert(texts, text)
if metabolism >= 1.65 then
qualityText = "extremelyHigh"
elseif metabolism >= 1.4 then
qualityText = "veryHigh"
elseif metabolism >= 1.1 then
qualityText = "high"
elseif metabolism >= 0.9 then
qualityText = "average"
elseif metabolism >= 0.7 then
qualityText = "low"
elseif metabolism >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
text = {
title = g_i18n:getText("rl_ui_metabolism"),
text = "rl_ui_genetics_" .. qualityText
}
table.insert(texts, text)
local health = genetics.health
if health >= 1.65 then
qualityText = "extremelyHigh"
elseif health >= 1.4 then
qualityText = "veryHigh"
elseif health >= 1.1 then
qualityText = "high"
elseif health >= 0.9 then
qualityText = "average"
elseif health >= 0.7 then
qualityText = "low"
elseif health >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
text = {
title = g_i18n:getText("rl_ui_health"),
text = "rl_ui_genetics_" .. qualityText
}
table.insert(texts, text)
local fertility = genetics.fertility
if fertility >= 1.65 then
qualityText = "extremelyHigh"
elseif fertility >= 1.4 then
qualityText = "veryHigh"
elseif fertility >= 1.1 then
qualityText = "high"
elseif fertility >= 0.9 then
qualityText = "average"
elseif fertility >= 0.7 then
qualityText = "low"
elseif fertility >= 0.35 then
qualityText = "veryLow"
elseif fertility > 0 then
qualityText = "extremelyLow"
else
qualityText = "infertile"
end
text = {
title = g_i18n:getText("rl_ui_fertility"),
text = "rl_ui_genetics_" .. qualityText
}
table.insert(texts, text)
local meat = genetics.quality
if meat >= 1.65 then
qualityText = "extremelyHigh"
elseif meat >= 1.4 then
qualityText = "veryHigh"
elseif meat >= 1.1 then
qualityText = "high"
elseif meat >= 0.9 then
qualityText = "average"
elseif meat >= 0.7 then
qualityText = "low"
elseif meat >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
text = {
title = g_i18n:getText("rl_ui_meat"),
text = "rl_ui_genetics_" .. qualityText
}
table.insert(texts, text)
if genetics.productivity ~= nil then
local productivity = genetics.productivity
if productivity >= 1.65 then
qualityText = "extremelyHigh"
elseif productivity >= 1.4 then
qualityText = "veryHigh"
elseif productivity >= 1.1 then
qualityText = "high"
elseif productivity >= 0.9 then
qualityText = "average"
elseif productivity >= 0.7 then
qualityText = "low"
elseif productivity >= 0.35 then
qualityText = "veryLow"
else
qualityText = "extremelyLow"
end
local productivityTitle = ""
if self.animalTypeIndex == AnimalType.COW then productivityTitle = g_i18n:getText("rl_ui_milk") end
if self.animalTypeIndex == AnimalType.SHEEP then productivityTitle = g_i18n:getText("rl_ui_wool") end
if self.animalTypeIndex == AnimalType.CHICKEN then productivityTitle = g_i18n:getText("rl_ui_eggs") end
text = {
title = productivityTitle,
text = "rl_ui_genetics_" .. qualityText
}
table.insert(texts, text)
end
return texts
end
function Animal:showMonitorInfo(box)
if not self.monitor.active and not self.monitor.removed then return end
local daysPerMonth = g_currentMission.environment.daysPerPeriod
box:addLine(g_i18n:getText("rl_ui_monitorFee"), string.format(g_i18n:getText("rl_ui_feePerMonth"), g_i18n:formatMoney(self.monitor.fee, 2, true, true)))
box:addLine(g_i18n:getText("infohud_health"), string.format("%d%%", self.health))
if self.clusterSystem ~= nil and self.clusterSystem.owner.spec_husbandryMilk ~= nil and self.gender ~= nil and self.gender == "female" and self.age >= 12 then
if self.isLactating ~= nil then box:addLine(g_i18n:getText("rl_ui_lactating"), self.isLactating and g_i18n:getText("rl_ui_yes") or g_i18n:getText("rl_ui_no")) end
end
box:addLine(g_i18n:getText("rl_ui_weight"), string.format("%.2f", self.weight) .. "kg")
box:addLine(g_i18n:getText("rl_ui_targetWeight"), string.format("%.2f", self.targetWeight) .. "kg")
box:addLine(g_i18n:getText("rl_ui_valuePerKilo"), g_i18n:formatMoney(self:getSellPrice() / self.weight, 2, true, true))
for fillType, amount in pairs(self.input) do
box:addLine(g_i18n:getText("rl_ui_input_" .. fillType), string.format(g_i18n:getText("rl_ui_amountPerDay"), (amount * 24) / daysPerMonth))
end
for fillType, amount in pairs(self.output) do
local outputText = fillType
if fillType == "pallets" then
if self.animalTypeIndex == AnimalType.COW then outputText = "pallets_milk" end
if self.animalTypeIndex == AnimalType.SHEEP then outputText = self.subType == "GOAT" and "pallets_goatMilk" or "pallets_wool" end
if self.animalTypeIndex == AnimalType.CHICKEN then outputText = "pallets_eggs" end
end
box:addLine(g_i18n:getText("rl_ui_output_" .. outputText), string.format(g_i18n:getText("rl_ui_amountPerDay"), (amount * 24) / daysPerMonth))
end
end
function Animal:showDiseasesInfo(box)
for _, disease in pairs(self.diseases) do disease:showInfo(box) end
end
function Animal:getFillTypeTitle()
return g_fillTypeManager:getFillTypeTitleByIndex(self:getSubType().fillTypeIndex)
end
function Animal:getHealthFactor()
return self.health /100
end
function Animal:getReproductionFactor()
return self.reproduction / 100
end
function Animal:getSupportsReproduction()
return self:getSubType().supportsReproduction
end
function Animal:changeReproduction(delta)
local old = self.reproduction
self.reproduction = math.clamp(math.floor(self.reproduction + math.max(delta, 1)), 0, 100)
if math.abs(self.reproduction - old) > 0 then
--self:setDirty()
end
end
function Animal:getReproductionDelta()
local duration
if self.pregnancy ~= nil then duration = self.pregnancy.duration end
if duration == nil then
local subType = self:getSubType()
duration = subType.reproductionDurationMonth
end
if duration > 0 then
return math.floor((100 / duration) / g_currentMission.environment.daysPerPeriod)
end
return 0
end
function Animal:getCanReproduce()
if self.isPregnant or self.pregnancy ~= nil then return true end
local subType = self:getSubType()
if not subType.supportsReproduction or self.clusterSystem == nil then return false end
local canReproduce = RealisticLivestock.hasMaleAnimalInPen(self.clusterSystem.owner.spec_husbandryAnimals, self.subType, self) and (self.monthsSinceLastBirth > 2 or not self.isParent)
if self:getHealthFactor() >= subType.reproductionMinHealth then
canReproduce = canReproduce and self.age >= subType.reproductionMinAgeMonth
else
canReproduce = false
end
return canReproduce
end
function Animal:updateHealth(foodFactor)
local subType = self:getSubType()
local healthThresholdFactor = subType.healthThresholdFactor
local healthGenetics = self.genetics.health
local factor, delta = nil
if healthThresholdFactor < foodFactor then
factor = (foodFactor - healthThresholdFactor) / (1 - healthThresholdFactor)
delta = subType.healthIncreaseHour
else
factor = foodFactor / healthThresholdFactor - 1
delta = subType.healthDecreaseHour
end
local healthDelta = delta * factor * healthGenetics
if healthDelta ~= 0 then self.health = math.clamp(math.floor(self.health + healthDelta), 0, 100) end
self:updateWeight(foodFactor)
end
function Animal:updateWeight(foodFactor)
local subType = self:getSubType()
local minWeight = subType.minWeight
local targetWeight = self.targetWeight
local weight = self.weight
local metabolism = self.genetics.metabolism
local adultMonth = subType.reproductionMinAgeMonth * 1.5
local baseIncrease = ((targetWeight - minWeight) / adultMonth) / 24
local increase = baseIncrease * (self.gender == "female" and 0.6 or 1.0) * (1 + ((adultMonth - self.age) / 75)) * math.min(foodFactor * 1.25, 1)
if increase < 0 then metabolism = 1 + (1 - metabolism) end
increase = increase * metabolism
if self.isCastrated then increase = increase * 1.15 end
if self.clusterSystem ~= nil and self.clusterSystem.owner ~= nil and self.clusterSystem.owner.spec_husbandryMilk ~= nil and self.isLactating then increase = increase * 0.75 end
local decrease = 0
if weight > targetWeight then decrease = (weight - targetWeight) / (metabolism * 25) end
if foodFactor == 0 then
if weight < targetWeight then
decrease = (targetWeight - weight) / ((1 - (metabolism - 1)) * 150)
elseif weight > targetWeight then
decrease = decrease + ((weight - targetWeight) / ((1 - (metabolism - 1)) * 150))
end
end
self.weight = math.max(self.weight + increase - decrease, 0.001)
local minWeightForAge = minWeight * (math.min(self.age, subType.reproductionMinAgeMonth * 1.5) + 0.5) * 0.5
if self.weight < minWeightForAge then self.health = math.clamp(self.health - (((minWeightForAge - self.weight) / minWeightForAge) * 0.2), 0, 100) end
end
function Animal:onPeriodChanged()
self.monthsSinceLastBirth = self.monthsSinceLastBirth + 1
local totalTreatmentCost = 0
for i = #self.diseases, 1, -1 do
local died, treatmentCost = self.diseases[i]:onPeriodChanged(self, self.deathEnabled)
totalTreatmentCost = totalTreatmentCost + treatmentCost
if died then return totalTreatmentCost end
end
return totalTreatmentCost
end
function Animal:onDayChanged(spec, isServer, day, month, year, currentDayInPeriod, daysPerPeriod, isSaleAnimal)
if g_server ~= nil then g_diseaseManager:onDayChanged(self) end
self:setRecentlyBoughtByAI(false)
local birthday = self.birthday
if day == nil then
local environment = g_currentMission.environment
month = environment.currentPeriod + 2
currentDayInPeriod = environment.currentDayInPeriod
if month > 12 then month = month - 12 end
daysPerPeriod = environment.daysPerPeriod
day = 1 + math.floor((currentDayInPeriod - 1) * (RealisticLivestock.DAYS_PER_MONTH[month] / daysPerPeriod))
year = environment.currentYear
end
if birthday ~= nil and birthday.lastAgeMonth ~= month then
if birthday.day <= day or currentDayInPeriod == daysPerPeriod then
self:increaseAge()
self.birthday.lastAgeMonth = month
end
elseif birthday == nil and day == 1 then
self:increaseAge()
end
local children = 0
local deadAnimals = 0
local childrenSold = 0
local childrenSoldAmount = 0
if self.animalTypeIndex == AnimalType.HORSE and not isSaleAnimal then
local ridingFactor = self:getRidingFactor()
local ridingThresholdFactor = self:getSubType().ridingThresholdFactor
local factor, delta
if ridingThresholdFactor < ridingFactor then
factor = (ridingFactor - ridingThresholdFactor) / (1 - ridingThresholdFactor)
delta = 25
else
factor = ridingFactor / ridingThresholdFactor - 1
delta = 10
end
self:changeFitness(delta * factor * g_currentMission.environment.timeAdjustment)
self:resetRiding()
self:changeDirt(10)
end
local insemination = self.insemination
if insemination ~= nil and g_server ~= nil then
local fertility = self.genetics.fertility
local childNum = self:generateRandomOffspring()
if childNum > 0 and math.random() >= (2 - fertility) * 0.25 and math.random() <= insemination.success * (math.random(80, 120) / 100) then
self:addMessage("INSEMINATION_SUCCESS")
g_server:broadcastEvent(AnimalInseminationResultEvent.new(self.clusterSystem.owner, self, true))
self:createPregnancy(childNum, month, year, {
["uniqueId"] = string.format("%s %s %s", RealisticLivestock.AREA_CODES[insemination.country].code, insemination.farmId, insemination.uniqueId),
["metabolism"] = insemination.genetics.metabolism,
["quality"] = insemination.genetics.quality,
["health"] = insemination.genetics.health,
["fertility"] = insemination.genetics.fertility,
["productivity"] = insemination.genetics.productivity
})
else
self:addMessage("INSEMINATION_FAIL")
g_server:broadcastEvent(AnimalInseminationResultEvent.new(self.clusterSystem.owner, self, true))
end
end
self.insemination = nil
if isSaleAnimal or self.clusterSystem ~= nil then
if self.reproduction > 0 and (self.pregnancy == nil or self.pregnancy.pregnancies == nil) then
self.pregnancy = nil
self.reproduction = 0
end
if self.isPregnant then
self:changeReproduction(self:getReproductionDelta())
if self.reproduction >= 100 and g_server ~= nil and self.pregnancy ~= nil and spec ~= nil then
if self.impregnatedBy == nil then
self.impregnatedBy = {
uniqueId = "-1",
metabolism = self.genetics.metabolism,
quality = self.genetics.quality,
health = self.genetics.health,
fertility = self.genetics.fertility,
productivity = self.genetics.productivity or nil
}
end
if self.impregnatedBy.uniqueId == nil then self.impregnatedBy.uniqueId = "-1" end
if self.impregnatedBy.metabolism == nil then self.impregnatedBy.metabolism = self.genetics.metabolism end
if self.impregnatedBy.quality == nil then self.impregnatedBy.quality = self.genetics.meatQuality end
if self.impregnatedBy.health == nil then self.impregnatedBy.health = self.genetics.health end
if self.impregnatedBy.fertility == nil then self.impregnatedBy.fertility = self.genetics.fertility end
self.isLactating = false
self.isPregnant = false
local parentDied = false
children, parentDied, childrenSold, childrenSoldAmount = self:reproduce(spec, day, month, year, isSaleAnimal)
self.reproduction = 0
if parentDied then deadAnimals = 1 end
self.impregnatedBy = nil
self.pregnancy = nil
end
elseif g_server ~= nil and not isSaleAnimal and self:getCanReproduce() then
local fertility = self.genetics.fertility
local childNum = self:generateRandomOffspring()
if math.random() >= (2 - fertility) * 0.5 and childNum > 0 then self:createPregnancy(childNum, month, year) end
end
end
local lowHealthDeath, oldDeath, randomDeath, randomDeathMoney = 0, 0, 0, 0
if self.deathEnabled and g_server ~= nil and (self.clusterSystem == nil or self.clusterSystem.owner:getOwnerFarmId() ~= FarmManager.INVALID_FARM_ID) then
lowHealthDeath = self:CalculateLowHealthMonthlyAnimalDeaths()
if lowHealthDeath == 0 then oldDeath = self:CalculateOldAgeMonthlyAnimalDeaths() end
if spec ~= nil and lowHealthDeath == 0 and oldDeath == 0 then randomDeath, randomMoney = self:CalculateRandomMonthlyAnimalDeaths(spec) end
if lowHealthDeath > 0 or oldDeath > 0 or randomDeath > 0 then g_server:broadcastEvent(AnimalDeathEvent.new(self.clusterSystem ~= nil and self.clusterSystem.owner or nil, self)) end
end
return children, deadAnimals, childrenSold, childrenSoldAmount, lowHealthDeath, oldDeath, randomDeath, randomDeathMoney
end
function Animal:createPregnancy(childNum, month, year, father)
local fertility = self.genetics.fertility
self.isPregnant = true
if father == nil then
father = {
uniqueId = "-1",
metabolism = 1.0,
quality = 1.0,
health = 1.0,
fertility = 1.0,
productivity = 1.0
}
local fatherSubTypeIndex
for _, animal in pairs(self.clusterSystem:getAnimals()) do
if animal.gender ~= "male" or animal.isCastrated or animal.genetics.fertility <= 0 or animal:getIdentifiers() == self.fatherId then continue end
if animal.subType == "BULL_WATERBUFFALO" and self.subType ~= "COW_WATERBUFFALO" then continue end
if animal.subType == "RAM_GOAT" and self.subType ~= "GOAT" then continue end
if self.subType == "COW_WATERBUFFALO" and animal.subType ~= "BULL_WATERBUFFALO" then continue end
if self.subType == "GOAT" and animal.subType ~= "RAM_GOAT" then continue end
local animalType = animal.animalTypeIndex
local animalSubType = animal:getSubType()
local maxFertilityMonth = (animalType == AnimalType.COW and 132) or (animalType == AnimalType.SHEEP and 72) or (animalType == AnimalType.HORSE and 300) or (animalType == AnimalType.CHICKEN and 1000) or (animalType == AnimalType.PIG and 48) or 120
maxFertilityMonth = maxFertilityMonth * animal.genetics.fertility
if animalSubType.reproductionMinAgeMonth ~= nil and animal:getAge() >= animalSubType.reproductionMinAgeMonth and animal:getAge() < maxFertilityMonth then
fatherSubTypeIndex = animal.subTypeIndex
father.uniqueId = animal:getIdentifiers()
father.metabolism = animal.genetics.metabolism
father.quality = animal.genetics.quality
father.health = animal.genetics.health
father.fertility = animal.genetics.fertility
father.productivity = animal.genetics.productivity or nil
father.animal = animal
break
end
end
end
self.impregnatedBy = father
self.reproduction = 0
self:changeReproduction(self:getReproductionDelta())
local genetics = self.genetics
local motherMetabolism = genetics.metabolism
local fatherMetabolism = father.metabolism
local minMetabolism = motherMetabolism >= fatherMetabolism and fatherMetabolism or motherMetabolism
local maxMetabolism = motherMetabolism < fatherMetabolism and fatherMetabolism or motherMetabolism
if maxMetabolism == minMetabolism then maxMetabolism = maxMetabolism + 0.01 end
local motherMeat = genetics.quality
local fatherMeat = father.quality
local minMeat = motherMeat >= fatherMeat and fatherMeat or motherMeat
local maxMeat = motherMeat < fatherMeat and fatherMeat or motherMeat
if maxMeat == minMeat then maxMeat = maxMeat + 0.01 end
local motherHealth = genetics.health
local fatherHealth = father.health
local minHealth = motherHealth >= fatherHealth and fatherHealth or motherHealth
local maxHealth = motherHealth < fatherHealth and fatherHealth or motherHealth
if maxHealth == minHealth then maxHealth = maxHealth + 0.01 end
local motherFertility = genetics.fertility
local fatherFertility = father.fertility
local minFertility = motherFertility >= fatherFertility and fatherFertility or motherFertility
local maxFertility = motherFertility < fatherFertility and fatherFertility or motherFertility
if maxFertility == minFertility then maxFertility = maxFertility + 0.01 end
local motherProductivity
local fatherProductivity
local minProductivity
local maxProductivity
if genetics.productivity ~= nil then
motherProductivity = genetics.productivity
fatherProductivity = father.productivity or 1
minProductivity = motherProductivity >= fatherProductivity and fatherProductivity or motherProductivity
maxProductivity = motherProductivity < fatherProductivity and fatherProductivity or motherProductivity
if maxProductivity == minProductivity then maxProductivity = maxProductivity + 0.01 end
end
local mDiseases, fDiseases = self.diseases, father.animal ~= nil and father.animal.diseases or {}
local diseases = {}
for _, disease in pairs(mDiseases) do table.insert(diseases, { ["parent"] = father.animal, ["disease"] = disease }) end
for _, disease in pairs(fDiseases) do
local hasDisease = false
for _, mDisease in pairs(mDiseases) do
if mDisease.type.title == disease.type.title then
hasDisease = true
break
end
end
if not hasDisease then table.insert(diseases, { ["parent"] = self, ["disease"] = disease }) end
end
local children = {}
local hasMale, hasFemale = false, false
for i = 1, childNum do
local gender = math.random() >= 0.5 and "male" or "female"
local subTypeIndex
if fatherSubTypeIndex ~= nil and math.random() >= 0.5 then
subTypeIndex = fatherSubTypeIndex + (gender == "male" and 0 or -1)
else
subTypeIndex = self.subTypeIndex + (gender == "male" and 1 or 0)
end
local child = Animal.new(-1, 100, 0, gender, subTypeIndex, 0, false, false, false, nil, nil, self:getIdentifiers(), father.uniqueId)
local metabolism = math.random(minMetabolism * 100, maxMetabolism * 100) / 100
local quality = math.random(minMeat * 100, maxMeat * 100) / 100
local healthGenetics = math.random(minHealth * 100, maxHealth * 100) / 100
local fertility = 0
if math.random() > 0.001 then fertility = math.random(minFertility * 100, maxFertility * 100) / 100 end
local productivity = nil
if genetics.productivity ~= nil then productivity = math.random(minProductivity * 100, maxProductivity * 100) / 100 end
child:setGenetics({
["metabolism"] = metabolism,
["quality"] = quality,
["health"] = healthGenetics,
["fertility"] = fertility,
["productivity"] = productivity
})
for _, disease in pairs(diseases) do
disease.disease:affectReproduction(child, disease.parent)
end
table.insert(children, child)
if gender == "male" then
hasMale = true
else
hasFemale = true
end
end
if self.animalTypeIndex == AnimalType.COW and hasMale and hasFemale then
for _, child in pairs(children) do
if child.gender == "female" and math.random() >= 0.03 then child.genetics.fertility = 0 end
end
end
local reproductionDuration = self:getSubType().reproductionDurationMonth
if math.random() >= 0.99 then
if math.random() >= 0.95 then
reproductionDuration = reproductionDuration + (math.random() >= 0.75 and -2 or 2)
else
reproductionDuration = reproductionDuration + (math.random() >= 0.85 and -1 or 1)
end
reproductionDuration = math.clamp(reproductionDuration, 2, 12)
end
local expectedYear = year + math.floor(reproductionDuration / 12)
local expectedMonth = month + (reproductionDuration % 12)
while expectedMonth > 12 do
expectedMonth = expectedMonth - 12
expectedYear = expectedYear + 1
end
local expectedDay = math.random(1, RealisticLivestock.DAYS_PER_MONTH[expectedMonth])
self.pregnancy = {
["duration"] = reproductionDuration,
["expected"] = {
["day"] = expectedDay,
["month"] = expectedMonth,
["year"] = expectedYear
},
["pregnancies"] = children
}
g_server:broadcastEvent(AnimalPregnancyEvent.new(self.clusterSystem ~= nil and self.clusterSystem.owner or nil, self))
end
function Animal:getAnimalTypeIndex()
return g_currentMission.animalSystem:getTypeIndexBySubTypeIndex(self.subTypeIndex)
end
function Animal:generateRandomOffspring()
local animalSystem = g_currentMission.animalSystem
local animalType = animalSystem:getTypeByIndex(self.animalTypeIndex)
local fertility = self.genetics.fertility
local fertilityValue = fertility * (animalType.fertility:get(self.age) / 100)
if math.random() >= fertilityValue then return 0 end
local factor = 0.75 + fertility / 4
if math.random() >= 0.25 then return animalType.pregnancy.average end
local amount = animalType.pregnancy.get(math.random() * factor)
return amount
end
local function sortChildSellPrices(a, b)
return a.sellPrice > b.sellPrice
end
function Animal:reproduce(spec, day, month, year, isSaleAnimal)
if self.pregnancy == nil or self.pregnancy.pregnancies == nil then return 0, false, 0, 0 end
local pregnancies = self.pregnancy.pregnancies
local freeSlots = isSaleAnimal and 100 or (spec.maxNumAnimals - spec:getNumOfAnimals())
local childNum = #pregnancies
local animalsToSell = 0
local subType = self:getSubType()
local animalType = self:getAnimalTypeIndex()
local parentDied = false
if freeSlots - childNum < 0 then
animalsToSell = childNum - freeSlots
end
self.monthsSinceLastBirth = 0
if childNum > 0 then
self.isParent = true
if animalType == AnimalType.COW or self.subType == "GOAT" then self.isLactating = true end
end
childNum = childNum - animalsToSell
local fatherFull
if not isSaleAnimal and self.impregnatedBy ~= nil and self.impregnatedBy.uniqueId ~= nil and self.impregnatedBy.uniqueId ~= "-1" then
local placeables = g_currentMission.placeableSystem.placeables
for _, placeable in ipairs(placeables) do
if placeable.spec_husbandryAnimals == nil and placeable.spec_livestockTrailer == nil then continue end
local clusterSystem = nil
if placeable.spec_husbandryAnimals ~= nil then
clusterSystem = placeable.spec_husbandryAnimals.clusterSystem
elseif placeable.spec_livestockTrailer ~= nil then
clusterSystem = placeable.spec_livestockTrailer.clusterSystem
end
if clusterSystem == nil then continue end
local animals = clusterSystem:getAnimals()
for _, animal in ipairs(animals) do
if animal:getIdentifiers() ~= self.impregnatedBy.uniqueId then continue end
fatherFull = animal
break
end
if fatherFull ~= nil then break end
end
end
if fatherFull ~= nil then fatherFull.isParent = true end
local sellPrices = {}
local childrenToRemove = {}
local birthday = self.pregnancy.expected
local country = isSaleAnimal and self.birthday.country or RealisticLivestock.getMapCountryIndex()
for i, child in pairs(pregnancies) do
local genetics = child.genetics
local weightChance = math.random() * genetics.metabolism
local minWeight = child:getSubType().minWeight
local weight = minWeight + 0.5
if weightChance < 0.05 then
weight = weight * (math.random(70, 90) / 100)
elseif weightChance <= 0.95 then
weight = weight * (math.random(90, 110) / 100)
else
weight = weight * (math.random(110, 130) / 100)
end
if self.deathEnabled and math.random() >= genetics.health * (weight / minWeight) * 1.15 then
childNum = childNum - 1
animalsToSell = animalsToSell - 1
table.insert(childrenToRemove, i)
child.isDead = true
continue
end
child.weight = weight
child.age = 0
child:setBirthday({["day"] = day, ["month"] = month, ["year"] = year, ["country"] = country, ["lastAgeMonth"] = month})
if not isSaleAnimal then
child:setClusterSystem(self.clusterSystem)
child:setUniqueId()
else
child:setUniqueId(self.farmId)
end
local childInfo = {
farmId = child.farmId,
uniqueId = child.uniqueId
}
table.insert(self.children, childInfo)
if fatherFull ~= nil then table.insert(fatherFull.children, childInfo) end
table.insert(sellPrices, {
["index"] = i,
["sellPrice"] = child:getSellPrice()
})
end
if #childrenToRemove > 0 and math.random() >= 0.35 + self.genetics.health * 1.25 then parentDied = true end
table.sort(sellPrices, sortChildSellPrices)
local totalAnimalPrice = 0
for i = 1, animalsToSell do
local childToSell = sellPrices[i]
if childToSell == nil or pregnancies[childToSell.index] == nil then break end
table.insert(childrenToRemove, childToSell.index)
totalAnimalPrice = totalAnimalPrice + childToSell.sellPrice
end
table.sort(childrenToRemove)
for i = #childrenToRemove, 1, -1 do
table.remove(pregnancies, childrenToRemove[i])
end
local animalSystem = g_currentMission.animalSystem
for _, child in pairs(pregnancies) do
if isSaleAnimal then
animalSystem:addExistingSaleAnimal(child)
else
self.clusterSystem:addCluster(child)
end
end
if not isSaleAnimal then
local farmIndex = spec:getOwnerFarmId()
local animalTypeReal = animalSystem:getTypeByIndex(subType.typeIndex)
if animalTypeReal.statsBreedingName ~= nil then
local stats = g_currentMission:farmStats(farmIndex)
stats:updateStats(animalTypeReal.statsBreedingName, childNum)
end
end
self.impregnatedBy = nil
g_server:broadcastEvent(AnimalBirthEvent.new(self.clusterSystem ~= nil and self.clusterSystem.owner or nil, self, pregnancies, parentDied))
if #pregnancies > 0 then
if #pregnancies == 1 then
self:addMessage("PREGNANCY_SINGLE")
else
self:addMessage("PREGNANCY_MULTIPLE", { #pregnancies })
end
end
if animalsToSell > 0 then self:addMessage("PREGNANCY_SOLD", { animalsToSell, g_i18n:formatMoney(totalAnimalPrice, 2, true, true) }) end
if #childrenToRemove > 0 then self:addMessage("PREGNANCY_DIED", { #childrenToRemove }) end
if parentDied then self:die("rl_death_pregnancy") end
return childNum, parentDied, animalsToSell, totalAnimalPrice
end
function Animal:die(reason)
self.numAnimals = 0
self.isDead = true
if self.sale ~= nil then g_currentMission.animalSystem:removeSaleAnimal(self.animalTypeIndex, self.birthday.country, self.farmId, self.uniqueId) end
if self.isAIAnimal then g_currentMission.animalSystem:removeAIAnimal(self.animalTypeIndex, self.birthday.country, self.farmId, self.uniqueId) end
self:addMessage("DEATH", { reason or "rl_ui_unknownCauses" })
if self.clusterSystem ~= nil then self.clusterSystem:addPendingRemoveCluster(self) end
end
function Animal:CalculateLowHealthMonthlyAnimalDeaths()
if self.numAnimals <= 0 or self.isDead then
return 0
end
local deathChance = 0.01
local health = self.health
local healthGenetics = self.genetics.health
if health >= 80 then
return 0
end
if self.age < 6 then health = health - 10 end
deathChance = (0.5 * (2 - healthGenetics)) - (health / 100)
if math.random() <= deathChance then
self:die("rl_death_health")
return 1
end
return 0
end
function Animal:CalculateOldAgeMonthlyAnimalDeaths()
if self.numAnimals <= 0 or self.isDead then
return 0
end
local animalType = self.animalTypeIndex
local deathChance = 0.01
local age = self.age
local healthGenetics = self.genetics.health
local minAge = 20000
local maxAge = 30000
if animalType == AnimalType.COW then
-- cattle old age min: 15y (180m)
-- cattle old age max: 20y (240m)
minAge = 180
maxAge = 240
elseif animalType == AnimalType.SHEEP then
-- sheep old age min: 10y (120m)
-- sheep old age max: 12y (144m)
minAge = 120
maxAge = 144
elseif animalType == AnimalType.HORSE then
-- horse old age min: 25y (300m)
-- horse old age max: 30y (360m)
minAge = 300
maxAge = 360
elseif animalType == AnimalType.PIG then
-- pig old age min: 15y (180m)
-- pig old age max: 20y (240m)
minAge = 180
maxAge = 240
elseif animalType == AnimalType.CHICKEN then
-- chicken old age min: 5y (60m)
-- chicken old age max: 8y (96m)
minAge = 60
maxAge = 96
end
if age < minAge then
return 0
end
deathChance = 0.7 - ((maxAge - age) / 100)
if math.random() <= deathChance * (2 - healthGenetics) then
self:die("rl_death_age")
return 1
end
return 0
end
-- Animals can die randomly regardless of health such as due to broken legs - will be sold at a reduced price (lower quality meat)
function Animal:CalculateRandomMonthlyAnimalDeaths(spec)
if self.numAnimals <= 0 or self.isDead then
return 0, 0
end
local animalType = spec.animalTypeIndex
local animalsCanBeSold = true
local deathChance = 0.01
local temp = spec.minTemp
if animalType == AnimalType.COW then
deathChance = 0.002
if self.age < 6 then
deathChance = 0.0035
elseif self.age < 18 then
deathChance = 0.0024
end
elseif animalType == AnimalType.SHEEP then
deathChance = 0.003
if self.age < 3 then
deathChance = 0.0035
elseif self.age < 8 then
deathChance = 0.0032
end
elseif animalType == AnimalType.HORSE then
deathChance = 0.002
elseif animalType == AnimalType.PIG then
deathChance = 0.001
if self.age < 3 then
deathChance = 0.018
elseif self.age < 6 then
deathChance = 0.0075
end
elseif animalType == AnimalType.CHICKEN then
if self.age < 6 then
deathChance = 0.0012
else
deathChance = 0.0016
end
animalsCanBeSold = false
end
-- animals are more likely to die in cold weather, especially young animals due to ice, pneumonia etc
if temp ~= nil and temp < 10 and temp >= 0 then
deathChance = deathChance * (1 + (1 - (temp / 12)))
elseif temp ~= nil and temp < 0 then
deathChance = deathChance * (1 + (1 - (temp / 10)))
end
deathChance = deathChance * self.accidentsChance
if math.random() <= deathChance then
local animalPrice = 0
if animalsCanBeSold then animalPrice = self:getSellPrice() * 0.33 end
self:die("rl_death_accident")
return 1, animalPrice
end
return 0, 0
end
-- ##################################
-- HORSES
-- ##################################
function Animal:getHealthChangeFactor(foodFactor)
local fitnessFactor = self:getFitnessFactor()
if not Platform.gameplay.needHorseCleaning then
return 0.6 * foodFactor + 0.4 * fitnessFactor
end
local dirtFactor = 1 - self:getDirtFactor()
return 0.5 * foodFactor + 0.4 * fitnessFactor + 0.1 * dirtFactor
end
function Animal:getFitnessFactor()
return self.fitness / 100
end
function Animal:changeFitness(delta)
self.fitness = math.clamp(math.floor(self.fitness + delta), 0, 100)
end
function Animal:getRidingFactor()
return self.riding / 100
end
function Animal:setRiding(riding)
self.riding = riding
end
function Animal:resetRiding()
self.riding = 0
end
function Animal:changeRiding(delta)
self.riding = math.clamp(math.floor(self.riding + delta), 0, 100)
end
function Animal:getDirtFactor()
return self.dirt / 100
end
function Animal:changeDirt(delta)
self.dirt = math.clamp(math.floor(self.dirt + delta), 0, 100)
end
function Animal:getSellPrice()
local subType = self:getSubType()
local sellPrice = subType.sellPrice:get(self.age < 0 and 0 or self.age)
local weight = self.weight
local targetWeightForAge = ((self.targetWeight - subType.minWeight) / (subType.reproductionMinAgeMonth * 1.5)) * math.min(self.age + 1.5, subType.reproductionMinAgeMonth * 1.5) * 0.85
local weightFactor = 1 + ((weight - targetWeightForAge) / targetWeightForAge)
local meatFactor = self.genetics.quality
sellPrice = sellPrice + (sellPrice * 0.25 * (meatFactor - 1))
sellPrice = math.max(sellPrice + (((sellPrice * 0.6) / subType.targetWeight) * weight * (-1 + meatFactor)), 0.5)
if self.isCastrated then sellPrice = sellPrice + sellPrice * 0.15 end
for _, disease in pairs(self.diseases) do sellPrice = disease:modifyValue(sellPrice) end
if self.animalTypeIndex == AnimalType.HORSE then
return math.max(sellPrice * meatFactor * weightFactor * (0.3 + 0.5 * self:getHealthFactor() + 0.3 * self:getRidingFactor() + 0.2 * self:getFitnessFactor() - 0.2 * self:getDirtFactor()), sellPrice * 0.05)
end
return math.max(sellPrice * 0.6 + (sellPrice * 0.4 * weightFactor * (0.75 * self:getHealthFactor())) + sellPrice * (self.isLactating and 0.15 or 0) + sellPrice * (self.isPregnant and 0.25 or 0), sellPrice * 0.05)
end
function Animal:getDailyRidingTime()
return 300000
end
function Animal:getNumberOfImpregnatableFemalesForMale()
if self.gender == "female" or self.clusterSystem == nil then return 0 end
local subType = self:getSubType()
local animalType = self.animalTypeIndex
if (subType.reproductionMinAgeMonth ~= nil and subType.reproductionMinAgeMonth > self.age) or ((animalType == AnimalType.COW and self.age >= 132) or (animalType == AnimalType.SHEEP and self.age >= 72) or (animalType == AnimalType.HORSE and self.age >= 300) or (animalType == AnimalType.PIG and self.age >= 48)) then return 0 end
local i = 0
local id = self:getIdentifiers()
for _, animal in ipairs(self.clusterSystem:getAnimals()) do
if animal.gender == "male" or (animal.fatherId ~= nil and id == animal.fatherId) or animal.isPregnant then continue end
local s = animal:getSubType()
if s.reproductionMinAgeMonth == nil or s.reproductionMinAgeMonth > animal.age then continue end
if subType.name == "BULL_WATERBUFFALO" then
if s.name == "COW_WATERBUFFALO" then i = i + 1 end
elseif subType.name == "RAM_GOAT" then
if s.name == "GOAT" then i = i + 1 end
elseif s.name ~= "GOAT" and s.name ~= "COW_WATERBUFFALO" then
i = i + 1
end
end
return i
end
function Animal.onSettingChanged(name, state)
Animal[name] = state
end
function Animal:updateInput()
local subType = self:getSubType()
for fillType, input in pairs(subType.input) do
local litersPerDay = input:get(self.age)
if fillType == "food" then
if self.isLactating then litersPerDay = litersPerDay * 1.25 end
if self.reproduction ~= nil and self.reproduction > 0 and self.pregnancy ~= nil and self.pregnancy.pregnancies ~= nil then
litersPerDay = litersPerDay * math.pow(1 + ((self.reproduction / 100) / 5), #self.pregnancy.pregnancies)
end
if self.genetics.metabolism ~= nil then litersPerDay = litersPerDay * self.genetics.metabolism end
litersPerDay = litersPerDay * (RealisticLivestock_PlaceableHusbandryFood.foodScale or 1)
end
if fillType == "water" then
local litersPerDay = input:get(self.age)
if self.isLactating then litersPerDay = litersPerDay * 1.5 end
if self.reproduction ~= nil and self.reproduction > 0 and self.pregnancy ~= nil and self.pregnancy.pregnancies ~= nil then
litersPerDay = litersPerDay * math.pow(1 + ((self.reproduction / 100) / 5), #self.pregnancy.pregnancies)
end
end
self.input[fillType] = litersPerDay / 24
end
if water ~= nil then
local litersPerDay = water:get(self.age)
if self.isLactating then litersPerDay = litersPerDay * 1.5 end
if self.reproduction ~= nil and self.reproduction > 0 and self.pregnancy ~= nil and self.pregnancy.pregnancies ~= nil then
litersPerDay = litersPerDay * math.pow(1 + ((self.reproduction / 100) / 5), #self.pregnancy.pregnancies)
end
self.input.water = litersPerDay / 24
end
end
function Animal:updateOutput(temp)
local subType = self:getSubType()
for fillType, output in pairs(subType.output) do
local litersPerDay = 0
if output.curve ~= nil then
litersPerDay = output.curve:get(self.age)
else
litersPerDay = output:get(self.age)
end
if fillType == "pallets" then
local fillTypeIndex = output.fillType
local productivity = self.genetics.productivity or 1
if fillTypeIndex == FillType.WOOL then
if temp < 12 then litersPerDay = 0 end
elseif fillTypeIndex == FillType.GOATMILK then
local monthsSinceLastBirth = self.monthsSinceLastBirth or 12
local factor = 0.8
if monthsSinceLastBirth >= 10 or not self.isLactating or not self.isParent then
self.isLactating = false
factor = 0
elseif monthsSinceLastBirth <= 3 then
factor = factor + (monthsSinceLastBirth / 6)
else
factor = factor + ((11 - monthsSinceLastBirth) / 15)
end
litersPerDay = litersPerDay * factor
end
litersPerDay = litersPerDay * productivity
end
if fillType == "milk" then
local monthsSinceLastBirth = self.monthsSinceLastBirth or 12
local factor = 0.8
local productivity = self.genetics.productivity or 1
if monthsSinceLastBirth >= 10 or not self.isLactating or not self.isParent then
self.isLactating = false
factor = 0
elseif monthsSinceLastBirth <= 3 then
factor = factor + (monthsSinceLastBirth / 6)
else
factor = factor + ((11 - monthsSinceLastBirth) / 15)
end
litersPerDay = litersPerDay * factor * productivity
end
for _, disease in pairs(self.diseases) do litersPerDay = disease:modifyOutput(fillType, litersPerDay) end
self.output[fillType] = litersPerDay / 24
end
end
function Animal:getInput(inputType)
return self.input[inputType] or 0
end
function Animal:getOutput(outputType)
return self.output[outputType] or 0
end
function Animal:getHasName()
return self.name ~= nil and self.name ~= ""
end
function Animal:removeDisease(title)
for i, disease in pairs(self.diseases) do
if disease.type.title == title then
self:addMessage("DISEASE_CURED", { disease.type.name })
table.remove(self.diseases, i)
return
end
end
end
function Animal:addDisease(type, isCarrier, genes)
table.insert(self.diseases, Disease.new(type, isCarrier, genes))
self:addMessage("DISEASE_CONTRACTED", { type.name })
end
function Animal:getDisease(title)
for _, disease in pairs(self.diseases) do
if disease.type.title == title then return disease end
end
return nil
end
function Animal:addMessage(id, args)
if self.clusterSystem == nil or self.clusterSystem.owner == nil or self.clusterSystem.owner.addRLMessage == nil then return end
self.clusterSystem.owner:addRLMessage(id, self:getIdentifiers(), args)
end
function Animal:getIdentifiers()
return string.format("%s %s %s", RealisticLivestock.AREA_CODES[self.birthday.country].code, self.farmId, self.uniqueId)
end
function Animal:compareIdentifiers(identifiers)
return self:getIdentifiers() == identifiers
end
function Animal:setRecentlyBoughtByAI(value)
self.recentlyBoughtByAI = value
end
function Animal:getRecentlyBoughtByAI()
return self.recentlyBoughtByAI or false
end
function Animal:getMarked(key)
if key == nil then
for _, mark in pairs(self.marks) do
if mark.active then return true end
end
return false
end
return (self.marks[key].active) or false
end
function Animal:setMarked(key, active)
if key == nil then
for markKey, mark in pairs(self.marks) do self.marks[markKey].active = active end
return
end
self.marks[key].active = active
self:updateVisualMarker()
end
function Animal:getDefaultMarks()
return table.clone(RealisticLivestock.MARKS, 3)
end
function Animal:getHighestPriorityMark()
local highest
for key, mark in pairs(self.marks) do
if not mark.active then continue end
if highest == nil or highest.priority > mark.priority then highest = { ["key"] = key, ["priority"] = mark.priority } end
end
return highest.key
end
function Animal:getCanBeInseminatedByAnimal(animal)
if self.gender == "male" then return false, g_i18n:getText("rl_insemination_male") end
if self.pregnancy ~= nil or self.isPregnant then return false, g_i18n:getText("rl_insemination_pregnant") end
if self.animalTypeIndex ~= animal.typeIndex then return false, g_i18n:getText("rl_insemination_animalType") end
if self.insemination ~= nil then return false, g_i18n:getText("rl_insemination_inseminated") end
if self.age < self:getSubType().reproductionMinAgeMonth then return false, g_i18n:getText("rl_insemination_young") end
if self.monthsSinceLastBirth <= 2 then return false, g_i18n:getText("rl_insemination_recovering") end
if string.format("%s %s %s", RealisticLivestock.AREA_CODES[animal.country].code, animal.farmId, animal.uniqueId) == self.fatherId then return false, g_i18n:getText("rl_insemination_father") end
return true
end
function Animal:setInsemination(animal)
self.insemination = {
["country"] = animal.country,
["farmId"] = animal.farmId,
["uniqueId"] = animal.uniqueId,
["genetics"] = animal.genetics,
["name"] = animal.name,
["subTypeIndex"] = animal.subTypeIndex,
["success"] = animal.success
}
end
function Animal:getHasAnyDisease()
return g_diseaseManager.diseasesEnabled and #self.diseases > 0
end
function Animal:createVisual(husbandryId, animalId)
self.visualAnimal = VisualAnimal.new(self, husbandryId, animalId)
self.visualAnimal:load()
end
function Animal:deleteVisual()
if self.visualAnimal ~= nil then self.visualAnimal:delete() end
self.visualAnimal = nil
end
function Animal:setVisualEarTagColours(leftTag, leftText, rightTag, rightText)
if self.visualAnimal ~= nil then self.visualAnimal:setEarTagColours(leftTag, leftText, rightTag, rightText) end
end
function Animal:updateVisualRightEarTag()
if self.visualAnimal ~= nil then self.visualAnimal:setRightEarTag() end
end
function Animal:updateVisualLeftEarTag()
if self.visualAnimal ~= nil then self.visualAnimal:setLeftEarTag() end
end
function Animal:updateVisualMonitor()
if self.visualAnimal ~= nil then self.visualAnimal:setMonitor() end
end
function Animal:updateVisualMarker()
if self.visualAnimal ~= nil then self.visualAnimal:setMarker() end
end
================================================
FILE: src/RealisticLivestock_FSBaseMission.lua
================================================
RealisticLivestock_FSBaseMission = {}
local modDirectory = g_currentModDirectory
local modSettingsDirectory = g_currentModSettingsDirectory
local function fixInGameMenu(frame, pageName, uvs, position, predicateFunc)
local inGameMenu = g_gui.screenControllers[InGameMenu]
position = position or #inGameMenu.pagingElement.pages + 1
for k, v in pairs({pageName}) do
inGameMenu.controlIDs[v] = nil
end
for i = 1, #inGameMenu.pagingElement.elements do
local child = inGameMenu.pagingElement.elements[i]
if child == inGameMenu.pageAnimals then
position = i
break
end
end
inGameMenu[pageName] = frame
inGameMenu.pagingElement:addElement(inGameMenu[pageName])
inGameMenu:exposeControlsAsFields(pageName)
for i = 1, #inGameMenu.pagingElement.elements do
local child = inGameMenu.pagingElement.elements[i]
if child == inGameMenu[pageName] then
table.remove(inGameMenu.pagingElement.elements, i)
table.insert(inGameMenu.pagingElement.elements, position, child)
break
end
end
for i = 1, #inGameMenu.pagingElement.pages do
local child = inGameMenu.pagingElement.pages[i]
if child.element == inGameMenu[pageName] then
table.remove(inGameMenu.pagingElement.pages, i)
table.insert(inGameMenu.pagingElement.pages, position, child)
break
end
end
inGameMenu.pagingElement:updateAbsolutePosition()
inGameMenu.pagingElement:updatePageMapping()
inGameMenu:registerPage(inGameMenu[pageName], position, predicateFunc)
inGameMenu:addPageTab(inGameMenu[pageName], modDirectory .. "gui/icons.dds", GuiUtils.getUVs(uvs))
for i = 1, #inGameMenu.pageFrames do
local child = inGameMenu.pageFrames[i]
if child == inGameMenu[pageName] then
table.remove(inGameMenu.pageFrames, i)
table.insert(inGameMenu.pageFrames, position, child)
break
end
end
inGameMenu:rebuildTabList()
end
function RealisticLivestock_FSBaseMission:onStartMission()
g_gui.guis.AnimalScreen:delete()
g_gui:loadGui(modDirectory .. "gui/AnimalScreen.xml", "AnimalScreen", g_animalScreen)
local xmlFile = XMLFile.loadIfExists("RealisticLivestock", modSettingsDirectory .. "Settings.xml")
if xmlFile ~= nil then
local maxHusbandries = xmlFile:getInt("Settings.setting(0)#maxHusbandries", 2)
RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES = maxHusbandries
xmlFile:delete()
end
AnimalAIDialog.register()
AnimalInfoDialog.register()
DiseaseDialog.register()
FileExplorerDialog.register()
ProfileDialog.register()
NameInputDialog.register()
EarTagColourPickerDialog.register()
AnimalFilterDialog.register()
RLSettings.applyDefaultSettings()
local temp = self.environment.weather.temperatureUpdater.currentMin or 20
local isServer = self:getIsServer()
for _, placeable in pairs(self.husbandrySystem.placeables) do
local animals = placeable:getClusters()
for _, animal in pairs(animals) do
animal:updateInput()
animal:updateOutput(temp)
end
if isServer then placeable:updateInputAndOutput(animals) end
end
local realisticLivestockFrame = RealisticLivestockFrame.new()
g_gui:loadGui(modDirectory .. "gui/RealisticLivestockFrame.xml", "RealisticLivestockFrame", realisticLivestockFrame, true)
fixInGameMenu(realisticLivestockFrame, "realisticLivestockFrame", {260,0,256,256}, 4, function() return true end)
realisticLivestockFrame:initialize()
end
FSBaseMission.onStartMission = Utils.prependedFunction(FSBaseMission.onStartMission, RealisticLivestock_FSBaseMission.onStartMission)
function RealisticLivestock_FSBaseMission:sendInitialClientState(connection, _, _)
local animalSystem = g_currentMission.animalSystem
for _, setting in pairs(RLSettings.SETTINGS) do
if not setting.ignore then setting.state = setting.state or setting.default end
end
connection:sendEvent(RL_BroadcastSettingsEvent.new())
connection:sendEvent(AnimalSystemStateEvent.new(animalSystem.countries, animalSystem.animals, animalSystem.aiAnimals))
connection:sendEvent(DewarManagerStateEvent.new())
connection:sendEvent(HusbandryMessageStateEvent.new(g_currentMission.husbandrySystem.placeables))
end
FSBaseMission.sendInitialClientState = Utils.prependedFunction(FSBaseMission.sendInitialClientState, RealisticLivestock_FSBaseMission.sendInitialClientState)
function RealisticLivestock_FSBaseMission:onDayChanged()
if not self:getIsServer() then return end
local husbandrySystem = self.husbandrySystem
for _, farm in pairs(g_farmManager:getFarms()) do
local husbandries = husbandrySystem:getPlaceablesByFarm(farm.farmId)
local wages = 0
for _, husbandry in pairs(husbandries) do
local aiManager = husbandry:getAIManager()
if aiManager ~= nil then wages = wages + (aiManager.wage or 0) end
end
if wages > 0 then self:addMoney(-wages, farm.farmId, MoneyType.HERDSMAN_WAGES, true, true) end
end
end
FSBaseMission.onDayChanged = Utils.appendedFunction(FSBaseMission.onDayChanged, RealisticLivestock_FSBaseMission.onDayChanged)
================================================
FILE: src/animals/husbandry/AnimalSystemStateEvent.lua
================================================
AnimalSystemStateEvent = {}
local AnimalSystemStateEvent_mt = Class(AnimalSystemStateEvent, Event)
InitEventClass(AnimalSystemStateEvent, "AnimalSystemStateEvent")
function AnimalSystemStateEvent.emptyNew()
local self = Event.new(AnimalSystemStateEvent_mt)
return self
end
function AnimalSystemStateEvent.new(countries, animals, aiAnimals)
local self = AnimalSystemStateEvent.emptyNew()
self.countries, self.animals, self.aiAnimals = countries, animals, aiAnimals
return self
end
function AnimalSystemStateEvent:readStream(streamId, connection)
local numCountries = streamReadUInt8(streamId)
local countries = {}
for i = 1, numCountries do
local farms = {}
local numFarms = streamReadUInt8(streamId)
for j = 1, numFarms do
local semenPrice = streamReadFloat32(streamId)
local quality = streamReadFloat32(streamId)
local id = streamReadUInt32(streamId)
local numIds = streamReadUInt8(streamId)
local ids = {}
for k = 1, numIds do
local animalTypeIndex = streamReadUInt8(streamId)
local animalId = streamReadUInt32(streamId)
ids[animalTypeIndex] = animalId
end
table.insert(farms, {
["semenPrice"] = semenPrice,
["quality"] = quality,
["id"] = id,
["ids"] = ids
})
end
countries[i] = {
["farms"] = farms
}
end
self.countries = countries
self.animals = {}
local numAnimals = streamReadUInt8(streamId)
for i = 1, numAnimals do
local animals = {}
local numSaleAnimals = streamReadUInt16(streamId)
for j = 1, numSaleAnimals do
local animal = Animal.new()
local success = animal:readStream(streamId, connection)
local day = streamReadUInt16(streamId)
animal.sale = {
["day"] = day
}
table.insert(animals, animal)
end
self.animals[i] = animals
end
self.aiAnimals = {}
local numAIAnimals = streamReadUInt8(streamId)
for i = 1, numAIAnimals do
local animals = {}
local num = streamReadUInt16(streamId)
for j = 1, num do
local animal = Animal.new()
local success = animal:readStream(streamId, connection)
animal.isAIAnimal = true
animal.success = streamReadFloat32(streamId) or 0.65
animal.favouritedBy = {}
local numUsers = streamReadUInt8(streamId)
for k = 1, numUsers do
local userId = streamReadString(streamId)
local favourite = streamReadBool(streamId)
animal.favouritedBy[userId] = favourite
end
table.insert(animals, animal)
end
self.aiAnimals[i] = animals
end
self:run(connection)
end
function AnimalSystemStateEvent:writeStream(streamId, connection)
local countries = self.countries
streamWriteUInt8(streamId, #countries)
for i = 1, #countries do
local farms = countries[i].farms
streamWriteUInt8(streamId, #farms)
for _, farm in pairs(farms) do
streamWriteFloat32(streamId, farm.semenPrice)
streamWriteFloat32(streamId, farm.quality)
streamWriteUInt32(streamId, farm.id)
local ids, numIds = farm.ids, 0
for animalTypeIndex, id in pairs(ids) do numIds = numIds + 1 end
streamWriteUInt8(streamId, numIds)
for animalTypeIndex, id in pairs(ids) do
streamWriteUInt8(streamId, animalTypeIndex)
streamWriteUInt32(streamId, id)
end
end
end
streamWriteUInt8(streamId, #self.animals)
for i = 1, #self.animals do
local animals = self.animals[i] or {}
streamWriteUInt16(streamId, #animals)
for _, animal in pairs(animals) do
local success = animal:writeStream(streamId, connection)
streamWriteUInt16(streamId, animal.sale.day)
end
end
streamWriteUInt8(streamId, #self.aiAnimals)
for i = 1, #self.aiAnimals do
local animals = self.aiAnimals[i] or {}
streamWriteUInt16(streamId, #animals)
for _, animal in pairs(animals) do
local success = animal:writeStream(streamId, connection)
streamWriteFloat32(streamId, animal.success or 0.65)
local numUsers = 0
for userId, _ in pairs(animal.favouritedBy) do numUsers = numUsers + 1 end
streamWriteUInt8(streamId, numUsers)
for userId, favourite in pairs(animal.favouritedBy) do
streamWriteString(streamId, userId)
streamWriteBool(streamId, favourite)
end
end
end
end
function AnimalSystemStateEvent:run(connection)
local animalSystem = g_currentMission.animalSystem
animalSystem.countries = self.countries
animalSystem.animals = self.animals
animalSystem.aiAnimals = self.aiAnimals
end
================================================
FILE: src/animals/husbandry/RealisticLivestock_AnimalNameSystem.lua
================================================
RealisticLivestock_AnimalNameSystem = {}
local modDirectory = g_currentModDirectory
function RealisticLivestock_AnimalNameSystem:loadMapData(_, _, missionInfo)
self.names = {}
self.femaleNames = {}
self.maleNames = {}
self.descriptions = {}
local xmlFile = XMLFile.loadIfExists("animalNames", modDirectory .. "xml/animalNames.xml")
if xmlFile == nil then return false end
xmlFile:iterate("animalNames.name", function(_, key)
local name = xmlFile:getString(key .. "#value")
if name == nil then return false end
local gender = xmlFile:getString(key .. "#gender", nil)
if gender == "male" then
table.insert(self.maleNames, name)
elseif gender == "female" then
table.insert(self.femaleNames, name)
else
table.insert(self.femaleNames, name)
table.insert(self.maleNames, name)
end
end)
xmlFile:iterate("animalNames.description", function(_, key)
local description = xmlFile:getString(key .. "#value")
table.insert(self.descriptions, description)
end)
xmlFile:delete()
return true
end
AnimalNameSystem.loadMapData = Utils.overwrittenFunction(AnimalNameSystem.loadMapData, RealisticLivestock_AnimalNameSystem.loadMapData)
function RealisticLivestock_AnimalNameSystem:getRandomName(_, gender)
if gender == nil then gender = "female" end
local names = gender == "male" and self.maleNames or self.femaleNames
if names == nil or #names == 0 then return nil end
local description = ""
if self.descriptions ~= nil and #self.descriptions > 0 and math.random() >= 0.65 then description = self.descriptions[math.random(1, #self.descriptions)] .. " " end
return description .. names[math.random(1, #names)]
end
AnimalNameSystem.getRandomName = Utils.overwrittenFunction(AnimalNameSystem.getRandomName, RealisticLivestock_AnimalNameSystem.getRandomName)
function AnimalNameSystem:getNamesAlphabetical(gender)
local names = table.clone(gender == "female" and self.femaleNames or self.maleNames)
table.sort(names, function(a, b) return a < b end)
return names
end
================================================
FILE: src/animals/husbandry/RealisticLivestock_AnimalSystem.lua
================================================
RealisticLivestock_AnimalSystem = {}
local modName = g_currentModName
local modDirectory = g_currentModDirectory
table.insert(FinanceStats.statNames, "monitorSubscriptions")
FinanceStats.statNameToIndex["monitorSubscriptions"] = #FinanceStats.statNames
AnimalSystem.BREED_TO_NAME = {
["HOLSTEIN"] = "Holstein",
["SWISS_BROWN"] = "Swiss Brown",
["ANGUS"] = "Angus",
["LIMOUSIN"] = "Limousin",
["HEREFORD"] = "Hereford",
["HIGHLAND"] = "Highland",
["WATER_BUFFALO"] = "Water Buffalo",
["LANDRACE"] = "Landrace",
["BLACK_PIED"] = "Black Pied",
["BERKSHIRE"] = "Berkshire",
["STEINSCHAF"] = "Steinschaf",
["SWISS_MOUNTAIN"] = "Swiss Mountain",
["BLACK_WELSH"] = "Black Welsh",
["GOAT"] = "Goat",
["GRAY"] = "Gray",
["PINTO"] = "Pinto",
["PALOMINO"] = "Palomino",
["CHESTNUT"] = "Chestnut",
["BAY"] = "Bay",
["BLACK"] = "Black",
["SEAL_BROWN"] = "Seal Brown",
["DUN"] = "Dun",
["CHICKEN"] = "Chicken",
["OTHER"] = "Unknown"
}
AnimalSystem.BREED_TO_MARKER_COLOUR = {
["HOLSTEIN"] = { 1, 0, 0 },
["SWISS_BROWN"] = { 1, 1, 0 },
["ANGUS"] = { 1, 1, 1 },
["LIMOUSIN"] = { 0, 0, 1 },
["HEREFORD"] = { 0, 0, 1 },
["WATER_BUFFALO"] = { 1, 1, 1 }
}
function RealisticLivestock_AnimalSystem:loadMapData(_, mapXml, mission, baseDirectory)
RLSettings.initialize()
RLSettings.validateCustomAnimalsConfiguration()
self.customEnvironment = modName
self.baseColours = {
["earTagLeft"] = { 0.8, 0.7, 0 },
["earTagRight"] = { 0.8, 0.7, 0 },
["earTagLeft_text"] = { 0, 0, 0 },
["earTagRight_text"] = { 0, 0, 0 }
}
local path = RLSettings.getAnimalsXMLPath() or (modDirectory .. "xml/animals.xml")
print(string.format("RealisticLivestock - using animals XML path \'%s\'", path))
local xmlFile = XMLFile.load("animals", path)
if xmlFile ~= nil then
local basePath = RLSettings.getAnimalsBasePath() or modDirectory
print(string.format("RealisticLivestock - using animals base path \'%s\'", basePath))
self:loadAnimals(xmlFile, basePath)
xmlFile:delete()
end
self.customEnvironment = mission.customEnvironment
local baseFilename = getXMLString(mapXml, "map.animals#filename")
if baseFilename == nil or baseFilename == "" then
Logging.xmlInfo(mapXml, "No animals xml given at \'map.animals#filename\'")
elseif #self.types == 0 or not RLSettings.getOverrideVanillaAnimals() then
local baseXmlFile = XMLFile.load("animals", Utils.getFilename(baseFilename, baseDirectory))
if baseXmlFile ~= nil then
self:loadAnimals(baseXmlFile, baseDirectory)
baseXmlFile:delete()
end
end
self.customEnvironment = modName
print("--------", string.format("RealisticLivestock - loaded %s animals:", #self.types))
for _, type in pairs(self.types) do
print("", string.format("- Animal Type: %s (%s subTypes)", type.name, #type.subTypes))
for i, subTypeIndex in pairs(type.subTypes) do
print(string.format("|--- SubType (%s): %s (%s)", i, self.subTypes[subTypeIndex].name, self.subTypes[subTypeIndex].gender))
end
end
print("", "--------")
self:loadColourConfigurations()
return #self.types > 0
end
AnimalSystem.loadMapData = Utils.overwrittenFunction(AnimalSystem.loadMapData, RealisticLivestock_AnimalSystem.loadMapData)
function RealisticLivestock_AnimalSystem:loadAnimals(_, xmlFile, directory)
for _, key in xmlFile:iterator("animals.animal") do
if #self.types >= 2 ^ AnimalSystem.SEND_NUM_BITS - 1 then
Logging.xmlWarning(xmlFile, "Maximum number of supported animal types reached. Ignoring remaining types")
return
end
local rawName = xmlFile:getString(key .. "#type")
if rawName == nil then
Logging.xmlError(xmlFile, "Missing animal type. \'%s\'", key)
return
end
local name = rawName:upper()
local rawConfigFilename = xmlFile:getString(key .. ".configFilename")
if rawConfigFilename == nil then
Logging.xmlError(xmlFile, "Missing config file for animal type \'%s\'. \'%s\'", name, key)
return
end
local configFilename = Utils.getFilename(rawConfigFilename, directory)
local animalType
if self.nameToTypeIndex[name] ~= nil then
animalType = self.nameToType[name]
else
local clusterClass = xmlFile:getString(key .. "#clusterClass")
if clusterClass == nil then
Logging.xmlError(xmlFile, "Missing animal clusterClass for \'%s\'!", key)
return
end
local statsBreedingName = xmlFile:getString(key .. "#statsBreeding")
local title = g_i18n:convertText(xmlFile:getString(key .. "#groupTitle"), self.customEnvironment)
local height = xmlFile:getFloat(key .. ".navMeshAgent#height")
local radius = xmlFile:getFloat(key .. ".navMeshAgent#radius")
local maxClimbMeters = xmlFile:getFloat(key .. ".navMeshAgent#maxClimbMeters")
local maxSlope = math.rad(xmlFile:getFloat(key .. ".navMeshAgent#maxSlope") or 15)
local sqmPerAnimal = xmlFile:getFloat(key .. ".pasture#sqmPerAnimal", 100)
local averageBuyAge = xmlFile:getInt(key .. "#averageBuyAge", 12)
local maxBuyAge = xmlFile:getInt(key .. "#maxBuyAge", 60)
local averageChildren = xmlFile:getInt(key .. ".pregnancy#average", 1)
local maxChildren = xmlFile:getInt(key .. ".pregnancy#max", 3)
local pregnancy = {}
local totalChance = 0
for i = 0, averageChildren - 1 do
totalChance = totalChance + (i / averageChildren) / maxChildren
table.insert(pregnancy, totalChance)
end
totalChance = totalChance + 0.5
table.insert(pregnancy, totalChance)
for i = averageChildren + 1, maxChildren - 1 do
totalChance = totalChance + (1 - totalChance) * 0.8
table.insert(pregnancy, totalChance)
end
table.insert(pregnancy, 1)
local function pregnancyFunction(value)
for i = 0, #pregnancy - 1 do
if pregnancy[i + 1] > value then return i end
end
return 0
end
local fertility = self:loadAnimCurve(xmlFile, key .. ".fertility")
if fertility == nil then
fertility = AnimCurve.new(linearInterpolator1)
for i = 0, 120, 6 do
fertility:addKeyframe({
i <= 12 and 0 or (i <= 30 and (900 + i)) or (900 - i * 3),
["time"] = i
})
end
fertility:addKeyframe({
0,
["time"] = 121
})
end
animalType = {
["name"] = name,
["groupTitle"] = title,
["typeIndex"] = #self.types + 1,
["configFilename"] = configFilename,
["clusterClass"] = clusterClass == "AnimalCluster" and AnimalCluster or AnimalClusterHorse,
["statsBreedingName"] = statsBreedingName,
["navMeshAgentAttributes"] = {
["height"] = height,
["radius"] = radius,
["maxClimbMeters"] = maxClimbMeters,
["maxSlope"] = maxSlope
},
["sqmPerAnimal"] = sqmPerAnimal,
["subTypes"] = {},
["animals"] = {},
["averageBuyAge"] = averageBuyAge,
["maxBuyAge"] = maxBuyAge,
["colours"] = {
["earTagLeft"] = { 0.8, 0.7, 0 },
["earTagRight"] = { 0.8, 0.7, 0 },
["earTagLeft_text"] = { 0, 0, 0 },
["earTagRight_text"] = { 0, 0, 0 }
},
["pregnancy"] = {
["get"] = pregnancyFunction,
["average"] = averageChildren
},
["fertility"] = fertility,
["breeds"] = {}
}
end
if self:loadAnimalConfig(animalType, directory, configFilename) then
if self:loadSubTypes(animalType, xmlFile, key, directory) then
if self.nameToType[name] == nil then
table.insert(self.types, animalType)
self.nameToType[name] = animalType
self.nameToTypeIndex[name] = animalType.typeIndex
self.typeIndexToName[animalType.typeIndex] = name
end
end
end
end
end
AnimalSystem.loadAnimals = Utils.overwrittenFunction(AnimalSystem.loadAnimals, RealisticLivestock_AnimalSystem.loadAnimals)
function RealisticLivestock_AnimalSystem:loadAnimalConfig(_, animalType, directory, configFilename)
local xmlFile = XMLFile.load("animalsConfig", configFilename)
if xmlFile == nil then return false end
for _, key in xmlFile:iterator("animalHusbandry.animals.animal") do
local filename = xmlFile:getString(key .. ".assets#filename")
local filenamePosed = xmlFile:getString(key .. ".assets#filenamePosed")
local animal = {
["filename"] = Utils.getFilename(filename, directory),
["filenamePosed"] = Utils.getFilename(filenamePosed, directory)
}
if not fileExists(animal.filename) and string.contains(filename, "dataS") then animal.filename = filename end
if not fileExists(animal.filenamePosed) and string.contains(filenamePosed, "dataS") then animal.filenamePosed = filenamePosed end
if animal.filenamePosed == nil then
Logging.xmlError(xmlFile, "Missing \'filenamePosed\' for animal \'%s\'", key)
animal.filenamePosed = animal.filename
end
animal.variations = {}
for _, variationKey in xmlFile:iterator(key .. ".assets.texture") do
local variation = {}
local numTilesU = xmlFile:getInt(variationKey .. "#numTilesU", 1)
variation.numTilesU = math.max(numTilesU, 1)
local tileUIndex = xmlFile:getInt(variationKey .. "#tileUIndex", 0)
variation.tileUIndex = math.clamp(tileUIndex, 0, variation.numTilesU - 1)
local numTilesV = xmlFile:getInt(variationKey .. "#numTilesV", 1)
variation.numTilesV = math.max(numTilesV, 1)
local tileVIndex = xmlFile:getInt(variationKey .. "#tileVIndex", 0)
variation.tileVIndex = math.clamp(tileVIndex, 0, variation.numTilesV - 1)
variation.mirrorV = xmlFile:getBool(variationKey .. "#mirrorV", false)
variation.multi = xmlFile:getBool(variationKey .. "#multi", true)
table.insert(animal.variations, variation)
end
table.insert(animalType.animals, animal)
end
xmlFile:delete()
return true
end
AnimalSystem.loadAnimalConfig = Utils.overwrittenFunction(AnimalSystem.loadAnimalConfig, RealisticLivestock_AnimalSystem.loadAnimalConfig)
function RealisticLivestock_AnimalSystem:loadSubTypes(_, animalType, xmlFile, key, directory)
for _, subTypeKey in xmlFile:iterator(key .. ".subType") do
local rawName = xmlFile:getString(subTypeKey .. "#subType")
local requiredDLC = xmlFile:getString(subTypeKey .. "#requiredDLC")
if requiredDLC == nil or g_modNameToDirectory[g_uniqueDlcNamePrefix .. requiredDLC] ~= nil then
if rawName == nil then
Logging.xmlError(xmlFile, "Missing animal subtype. \'%s\'", subTypeKey)
break
end
local name = rawName:upper()
if self.nameToSubTypeIndex[name] ~= nil then continue end
local fillTypeName = xmlFile:getString(subTypeKey .. "#fillTypeName")
local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName)
if fillTypeIndex == nil then
Logging.xmlError(xmlFile, "FillType \'%s\' for animal subtype \'%s\' not defined!", fillTypeName, subTypeKey)
break
end
local subType = {
["name"] = name,
["subTypeIndex"] = #self.subTypes + 1,
["fillTypeIndex"] = fillTypeIndex,
["typeIndex"] = animalType.typeIndex,
["statsBreedingName"] = xmlFile:getString(subTypeKey .. "#statsBreeding") or animalType.statsBreedingName
}
table.insert(animalType.subTypes, subType.subTypeIndex)
if self:loadSubType(animalType, subType, xmlFile, subTypeKey, directory) then
table.insert(self.subTypes, subType)
self.nameToSubType[name] = subType
self.nameToSubTypeIndex[name] = subType.subTypeIndex
self.fillTypeIndexToSubType[fillTypeIndex] = subType
local breed = xmlFile:getString(subTypeKey .. "#breed", name)
subType.breed = breed
if animalType.breeds[breed] == nil then animalType.breeds[breed] = {} end
table.insert(animalType.breeds[breed], subType)
end
end
end
return true
end
AnimalSystem.loadSubTypes = Utils.overwrittenFunction(AnimalSystem.loadSubTypes, RealisticLivestock_AnimalSystem.loadSubTypes)
function RealisticLivestock_AnimalSystem:loadSubType(superFunc, animalType, subType, xmlFile, key, directory)
local returnValue = superFunc(self, animalType, subType, xmlFile, key, directory)
local height, radius = animalType.navMeshAgentAttributes.height, animalType.navMeshAgentAttributes.radius
subType.gender = xmlFile:getString(key .. "#gender", "female")
if directory ~= modDirectory and subType.gender == "female" then subType.gender = (string.contains(subType.name, "_MALE") or string.contains(subType.name, "BULL_") or string.contains(subType.name, "BOAR_") or string.contains(subType.name, "RAM_") or string.contains(subType.name, "BUCK_") or string.contains(subType.name, "STALLION_") or string.contains(subType.name, "ROOSTER_")) and "male" or "female" end
subType.maxWeight = xmlFile:getFloat(key .. "#maxWeight", height * radius * 750)
subType.targetWeight = xmlFile:getFloat(key .. "#targetWeight", height * radius * 300)
subType.minWeight = xmlFile:getFloat(key .. "#minWeight", height * radius * 50)
for _, visual in pairs(subType.visuals) do
if visual.textureIndexes == nil then continue end
local visualAnimal = table.clone(visual.visualAnimal, 10)
visualAnimal.variations = {}
for _, textureIndex in pairs(visual.textureIndexes) do table.insert(visualAnimal.variations, visual.visualAnimal.variations[textureIndex]) end
if #visualAnimal.variations > 0 then visual.visualAnimal = visualAnimal end
end
return returnValue
end
AnimalSystem.loadSubType = Utils.overwrittenFunction(AnimalSystem.loadSubType, RealisticLivestock_AnimalSystem.loadSubType)
function RealisticLivestock_AnimalSystem:loadVisualData(superFunc, animalType, xmlFile, key, baseDirectory)
local visualData = superFunc(self, animalType, xmlFile, key, baseDirectory)
if visualData == nil then return nil end
local earTagLeft = xmlFile:getString(key .. "#earTagLeft", nil)
local earTagRight = xmlFile:getString(key .. "#earTagRight", nil)
local noseRing = xmlFile:getString(key .. "#noseRing", nil)
local bumId = xmlFile:getString(key .. "#bumId", nil)
local monitor = xmlFile:getString(key .. "#monitor", nil)
local marker = xmlFile:getString(key .. "#marker", nil)
if earTagLeft ~= nil then visualData.earTagLeft = earTagLeft end
if earTagRight ~= nil then visualData.earTagRight = earTagRight end
if noseRing ~= nil then visualData.noseRing = noseRing end
if bumId ~= nil then visualData.bumId = bumId end
if monitor ~= nil then visualData.monitor = monitor end
if marker ~= nil then visualData.marker = marker end
if xmlFile:hasProperty(key .. ".textureIndexes") then
visualData.textureIndexes = {}
xmlFile:iterate(key .. ".textureIndexes.value", function(_, textureKey)
table.insert(visualData.textureIndexes, xmlFile:getInt(textureKey, 1))
end)
end
return visualData
end
AnimalSystem.loadVisualData = Utils.overwrittenFunction(AnimalSystem.loadVisualData, RealisticLivestock_AnimalSystem.loadVisualData)
function AnimalSystem:initialiseCountries()
self.maxDealerAnimals = self.maxDealerAnimals or 40
self.countries = {}
self.animals = {}
self.aiAnimals = {}
for _, animalType in pairs(self.types) do
self.animals[animalType.typeIndex] = {}
self.aiAnimals[animalType.typeIndex] = {}
end
for countryIndex, country in pairs(RealisticLivestock.AREA_CODES) do
self.countries[countryIndex] = {
["index"] = countryIndex,
["farms"] = {}
}
end
MoneyType.MONITOR_SUBSCRIPTIONS = MoneyType.register("monitorSubscriptions", "rl_ui_monitorSubscriptions")
MoneyType.LAST_ID = MoneyType.LAST_ID + 1
if self.isServer then g_messageCenter:subscribe(MessageType.HOUR_CHANGED, self.onHourChanged, self) end
g_messageCenter:subscribe(MessageType.DAY_CHANGED, self.onDayChanged, self)
g_messageCenter:subscribe(MessageType.PERIOD_CHANGED, self.onPeriodChanged, self)
end
function AnimalSystem:validateFarms(hasData)
if self.countries == nil then self.countries = {} end
local animalTypeIndexes = {}
for _, animalType in pairs(self.types) do table.insert(animalTypeIndexes, animalType.typeIndex) end
-- validate every country exists
for countryIndex, info in pairs(RealisticLivestock.AREA_CODES) do
if self.countries[countryIndex] == nil then
self.countries[countryIndex] = {
["index"] = countryIndex,
["farms"] = {}
}
end
end
-- validate all countries have at least 20 unique farms
local mapCountryIndex = RealisticLivestock.getMapCountryIndex()
for _, country in pairs(self.countries) do
local farmIds = {}
local farmsRequireId = {}
if country.index == mapCountryIndex then
for i, farm in pairs(g_farmManager.farms) do
local statistics = farm.stats.statistics
if statistics.farmId ~= nil then table.insert(farmIds, statistics.farmId) end
end
end
local isFirstCreation = #country.farms == 0
if #country.farms < 20 then
for i = #country.farms + 1, 20 do
local farm = { ["quality"] = math.random(250, 1750) / 1000, ["ids"] = {} }
farm.semenPrice = (math.random(75, 125) / 100) * farm.quality
for i = 0, math.random(0, math.min(3, #animalTypeIndexes)) do
--local randomProduce = math.random()
--local baseChance = 1 / (5 - i)
--if farm.cowId == nil and randomProduce <= baseChance then
-- farm.cowId = 0
--elseif farm.pigId == nil and randomProduce <= baseChance * (farm.cowId == nil and 2 or 1) then
-- farm.pigId = 0
--elseif farm.sheepId == nil and randomProduce <= baseChance * ((farm.cowId == nil and farm.pigId == nil and 3) or ((farm.cowId == nil or farm.pigId == nil) and 2) or 1) then
-- farm.sheepId = 0
--elseif farm.chickenId == nil and randomProduce <= baseChance * ((farm.cowId == nil and farm.pigId == nil and farm.sheepId == nil and 4) or (i == 1 and farm.horseId == nil and 3) or 2) then
-- farm.chickenId = 0
--else
-- farm.horseId = 0
--end
local randomAnimalTypeIndex = animalTypeIndexes[math.random(1, #animalTypeIndexes)]
local attempts = 0
while farm.ids[randomAnimalTypeIndex] ~= nil do
randomAnimalTypeIndex = animalTypeIndexes[math.random(1, #animalTypeIndexes)]
attempts = attempts + 1
if attempts > 20 then break end
end
farm.ids[randomAnimalTypeIndex] = 0
end
table.insert(country.farms, farm)
end
if isFirstCreation and country.index == mapCountryIndex then
-- validate there is at least 1 farm that produces each animal type
for i = 1, #animalTypeIndexes do
local randomFarmIndex = math.random(1, #country.farms)
country.farms[randomFarmIndex].ids[i] = country.farms[randomFarmIndex].ids[i] or 0
--if i == 1 then country.farms[randomFarmIndex].cowId = country.farms[randomFarmIndex].cowId or 0 end
--if i == 2 then country.farms[randomFarmIndex].pigId = country.farms[randomFarmIndex].pigId or 0 end
--if i == 3 then country.farms[randomFarmIndex].sheepId = country.farms[randomFarmIndex].sheepId or 0 end
--if i == 4 then country.farms[randomFarmIndex].chickenId = country.farms[randomFarmIndex].chickenId or 0 end
--if i == 5 then country.farms[randomFarmIndex].horseId = country.farms[randomFarmIndex].horseId or 0 end
end
end
end
for i, farm in pairs(country.farms) do
if farm.id ~= nil then
table.insert(farmIds, farm.id)
else
table.insert(farmsRequireId, i)
end
end
for _, farmIndex in pairs(farmsRequireId) do
local farmId = math.random(100000, 999999)
while table.find(farmIds, farmId) ~= nil do farmId = math.random(100000, 999999) end
country.farms[farmIndex].id = farmId
table.insert(farmIds, farmId)
end
end
-- validate there are at least 25 animals of each type for sale
if not hasData then
for animalTypeIndex, animals in pairs(self.animals) do
if #animals < self.maxDealerAnimals then
for i = #animals + 1, self.maxDealerAnimals do
local animal = self:createNewSaleAnimal(animalTypeIndex)
if animal ~= nil then table.insert(animals, animal) end
end
end
self.animals[animalTypeIndex] = animals
end
for animalTypeIndex, animals in pairs(self.aiAnimals) do
if #animals < 15 then
for i = #animals + 1, 15 do
local animal = self:createNewAIAnimal(animalTypeIndex)
if animal ~= nil then table.insert(animals, animal) end
end
end
self.aiAnimals[animalTypeIndex] = animals
end
end
end
function AnimalSystem:loadColourConfigurations()
local savegameIndex = g_careerScreen.savegameList.selectedIndex
local savegame = g_savegameController:getSavegame(savegameIndex)
if savegame == nil or savegame.savegameDirectory == nil then return false end
local xmlFile = XMLFile.loadIfExists("animalSystem", savegame.savegameDirectory .. "/animalSystem.xml")
if xmlFile == nil then return false end
xmlFile:iterate("animalSystem.animalTypes.type", function(_, key)
local name = xmlFile:getString(key .. "#name")
local earTagLeft = xmlFile:getVector(key .. "#earTagLeft", { 0.8, 0.7, 0 })
local earTagRight = xmlFile:getVector(key .. "#earTagRight", { 0.8, 0.7, 0 })
local earTagLeftText = xmlFile:getVector(key .. "#earTagLeftText", { 0, 0, 0 })
local earTagRightText = xmlFile:getVector(key .. "#earTagRightText", { 0, 0, 0 })
if self.nameToType[name] ~= nil then
self.nameToType[name].colours.earTagLeft = earTagLeft
self.nameToType[name].colours.earTagRight = earTagRight
self.nameToType[name].colours.earTagLeft_text = earTagLeftText
self.nameToType[name].colours.earTagRight_text = earTagRightText
end
end)
xmlFile:delete()
end
function AnimalSystem:loadFromXMLFile()
if g_currentMission.missionInfo == nil or g_currentMission.missionInfo.savegameDirectory == nil then return end
local xmlFile = XMLFile.loadIfExists("animalSystem", g_currentMission.missionInfo.savegameDirectory .. "/animalSystem.xml")
if xmlFile == nil then return false end
local hasData = false
xmlFile:iterate("animalSystem.countries.country", function(_, key)
local countryIndex = xmlFile:getInt(key .. "#index")
local farms = self.countries[countryIndex].farms
xmlFile:iterate(key .. ".farm", function(_, farmKey)
hasData = true
local farmId = xmlFile:getInt(farmKey .. "#id")
local cowId = xmlFile:getInt(farmKey .. "#cowId", nil)
local pigId = xmlFile:getInt(farmKey .. "#pigId", nil)
local sheepId = xmlFile:getInt(farmKey .. "#sheepId", nil)
local horseId = xmlFile:getInt(farmKey .. "#horseId", nil)
local chickenId = xmlFile:getInt(farmKey .. "#chickenId", nil)
local quality = xmlFile:getFloat(farmKey .. "#quality", math.random(250, 1750) / 1000)
local semenPrice = xmlFile:getFloat(farmKey .. "#semenPrice", (math.random(75, 125) / 100) * quality)
local ids = {}
-- compatibility with previous builds
if cowId ~= nil then ids[1] = cowId end
if pigId ~= nil then ids[2] = pigId end
if sheepId ~= nil then ids[3] = sheepId end
if horseId ~= nil then ids[4] = horseId end
if chickenId ~= nil then ids[5] = chickenId end
xmlFile:iterate(farmKey .. ".id", function(_, idKey)
local animalTypeIndex = xmlFile:getInt(idKey .. "#type", 1)
local lastId = xmlFile:getInt(idKey .. "#id", 0)
ids[animalTypeIndex] = lastId
end)
--table.insert(farms, { ["id"] = farmId, ["quality"] = quality, ["cowId"] = cowId, ["pigId"] = pigId, ["sheepId"] = sheepId, ["horseId"] = horseId, ["chickenId"] = chickenId })
table.insert(farms, { ["id"] = farmId, ["quality"] = quality, ["ids"] = ids, ["semenPrice"] = semenPrice })
end)
self.countries[countryIndex].farms = farms
end)
xmlFile:iterate("animalSystem.animals.animal", function(_, key)
local animal = Animal.loadFromXMLFile(xmlFile, key)
if animal ~= nil then
local animalTypeIndex = animal.animalTypeIndex
animal.sale = {
["day"] = xmlFile:getInt(key .. ".sale#day", 1),
--["month"] = xmlFile:getInt(key .. ".sale#month"),
--["year"] = xmlFile:getInt(key .. ".sale#year")
}
table.insert(self.animals[animalTypeIndex], animal)
end
end)
xmlFile:iterate("animalSystem.aiAnimals.animal", function(_, key)
local animal = Animal.loadFromXMLFile(xmlFile, key)
if animal ~= nil then
animal.favouritedBy = {}
animal.success = xmlFile:getFloat(key .. "#success", 0.65)
animal.isAIAnimal = true
xmlFile:iterate(key .. ".favourites.player", function(_, favKey)
local userId = xmlFile:getString(favKey .. "#userId", nil)
local value = xmlFile:getBool(favKey .. "#value", false)
if userId ~= nil then animal.favouritedBy[userId] = value end
end)
local animalTypeIndex = animal.animalTypeIndex
table.insert(self.aiAnimals[animalTypeIndex], animal)
end
end)
xmlFile:delete()
return hasData
end
function AnimalSystem:saveToXMLFile(path)
if path == nil then return end
local xmlFile = XMLFile.create("animalSystem", path, "animalSystem")
if xmlFile == nil then return end
xmlFile:setSortedTable("animalSystem.animalTypes.type", self.types, function (key, type)
xmlFile:setString(key .. "#name", type.name)
xmlFile:setVector(key .. "#earTagLeft", type.colours.earTagLeft)
xmlFile:setVector(key .. "#earTagLeftText", type.colours.earTagLeft_text)
xmlFile:setVector(key .. "#earTagRight", type.colours.earTagRight)
xmlFile:setVector(key .. "#earTagRightText", type.colours.earTagRight_text)
end)
xmlFile:setSortedTable("animalSystem.countries.country", self.countries, function (key, country)
xmlFile:setInt(key .. "#index", country.index)
for i = 1, #country.farms do
local farmKey = string.format("%s.farm(%d)", key, i - 1)
local farm = country.farms[i]
xmlFile:setInt(farmKey .. "#id", farm.id)
xmlFile:setFloat(farmKey .. "#quality", farm.quality)
xmlFile:setFloat(farmKey .. "#semenPrice", farm.semenPrice)
local j = 0
for animalTypeIndex, id in pairs(farm.ids) do
local idKey = farmKey .. ".id( " .. j .. ")"
xmlFile:setInt(idKey .. "#type", animalTypeIndex)
xmlFile:setInt(idKey .. "#id", id)
j = j + 1
end
end
end)
local allAnimals = {}
for _, animals in pairs(self.animals) do
for _, animal in pairs(animals) do
if animal.sale ~= nil and animal.sale.day ~= nil then table.insert(allAnimals, animal) end
end
end
xmlFile:setSortedTable("animalSystem.animals.animal", allAnimals, function (key, animal)
animal:saveToXMLFile(xmlFile, key)
xmlFile:setInt(key .. ".sale#day", animal.sale.day)
--xmlFile:setInt(key .. ".sale#month", animal.sale.month)
--xmlFile:setInt(key .. ".sale#year", animal.sale.year)
end)
local allAIAnimals = {}
for _, animals in pairs(self.aiAnimals) do
for _, animal in pairs(animals) do table.insert(allAIAnimals, animal) end
end
xmlFile:setSortedTable("animalSystem.aiAnimals.animal", allAIAnimals, function (key, animal)
animal:saveToXMLFile(xmlFile, key)
xmlFile:setFloat(key .. "#success", animal.success or 0.65)
local i = 0
for userId, value in pairs(animal.favouritedBy) do
if not value then continue end
local favKey = string.format("%s.favourites.player(%s)", key, i)
xmlFile:setString(favKey .. "#userId", userId)
xmlFile:setBool(favKey .. "#value", true)
i = i + 1
end
end)
xmlFile:save(false, true)
xmlFile:delete()
end
function AnimalSystem:createNewSaleAnimal(animalTypeIndex)
local animalType = self:getTypeByIndex(animalTypeIndex)
if animalType == nil then return nil end
local subTypeIndex = animalType.subTypes[math.random(1, #animalType.subTypes)]
local subType = self:getSubTypeByIndex(subTypeIndex)
local farmId, farmQuality, farmCountryIndex, lastAnimalId
local attemptedCountryIndexes = {}
while farmId == nil do
if #attemptedCountryIndexes == #self.countries then return nil end
local countryIndex
if #attemptedCountryIndexes == 0 and math.random() >= 0.12 then
countryIndex = RealisticLivestock.getMapCountryIndex()
else
countryIndex = math.random(1, #self.countries)
while table.find(attemptedCountryIndexes, countryIndex) ~= nil do
countryIndex = math.random(1, #self.countries)
end
end
table.insert(attemptedCountryIndexes, countryIndex)
local country = self.countries[countryIndex]
local validFarms = {}
for i = 1, #country.farms do
local farm = country.farms[i]
--if animalTypeIndex == AnimalType.COW and farm.cowId ~= nil then
-- table.insert(validFarms, i)
-- continue
--end
--if animalTypeIndex == AnimalType.PIG and farm.pigId ~= nil then
-- table.insert(validFarms, i)
-- continue
--end
--if animalTypeIndex == AnimalType.SHEEP and farm.sheepId ~= nil then
-- table.insert(validFarms, i)
-- continue
--end
--if animalTypeIndex == AnimalType.CHICKEN and farm.chickenId ~= nil then
-- table.insert(validFarms, i)
-- continue
--end
--if animalTypeIndex == AnimalType.HORSE and farm.horseId ~= nil then
-- table.insert(validFarms, i)
--end
if farm.ids[animalTypeIndex] ~= nil then table.insert(validFarms, i) end
end
if #validFarms == 0 then continue end
local farmIndex = validFarms[math.random(1, #validFarms)]
local farm = country.farms[farmIndex]
farmId = farm.id
farmQuality = farm.quality
farmCountryIndex = countryIndex
--if animalTypeIndex == AnimalType.COW then
-- country.farms[farmIndex].cowId = farm.cowId + 1
-- lastAnimalId = country.farms[farmIndex].cowId
--elseif animalTypeIndex == AnimalType.PIG then
-- country.farms[farmIndex].pigId = farm.pigId + 1
-- lastAnimalId = country.farms[farmIndex].pigId
--elseif animalTypeIndex == AnimalType.SHEEP then
-- country.farms[farmIndex].sheepId = farm.sheepId + 1
-- lastAnimalId = country.farms[farmIndex].sheepId
--elseif animalTypeIndex == AnimalType.CHICKEN then
-- country.farms[farmIndex].chickenId = farm.chickenId + 1
-- lastAnimalId = country.farms[farmIndex].chickenId
--elseif animalTypeIndex == AnimalType.HORSE then
-- country.farms[farmIndex].horseId = farm.horseId + 1
-- lastAnimalId = country.farms[farmIndex].horseId
--end
farm.ids[animalTypeIndex] = (farm.ids[animalTypeIndex] or 0) + 1
lastAnimalId = farm.ids[animalTypeIndex]
end
local averageBuyAge = animalType.averageBuyAge or 12
local maxBuyAge = animalType.maxBuyAge or 60
local age
if math.random() >= 0.5 then
age = math.random(averageBuyAge * 0.85, averageBuyAge * 1.15)
elseif math.random() >= 0.25 then
age = math.random(0, averageBuyAge * 0.85)
else
age = math.random(averageBuyAge * 1.15, maxBuyAge)
end
age = math.clamp(age, 0, maxBuyAge)
local viableReproductionMonths = age - (subType.reproductionMinAgeMonth + subType.reproductionDurationMonth)
local isParent, isPregnant, monthsSinceLastBirth = false, false, 12
local animalGender = subType.gender
if viableReproductionMonths >= 0 and math.random(0, 100) <= viableReproductionMonths then
isParent = true
monthsSinceLastBirth = math.random(0, viableReproductionMonths)
end
if animalGender == "female" and age - subType.reproductionMinAgeMonth >= 0 and math.random() >= 0.95 then isPregnant = true end
local uniqueId = tostring(lastAnimalId)
local idLen = string.len(uniqueId)
if idLen < 5 then
if idLen == 1 then
uniqueId = "1000" .. uniqueId
elseif idLen == 2 then
uniqueId = "100" .. uniqueId
elseif idLen == 3 then
uniqueId = "10" .. uniqueId
elseif idLen == 4 then
uniqueId = "1" .. uniqueId
end
end
local concatenatedId = farmId .. uniqueId
local checkDigit = (tonumber(concatenatedId)::number % 7) + 1
uniqueId = checkDigit .. uniqueId
local geneticsModifier = farmQuality * 1000
local genetics = {
["metabolism"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 0.25, 1.75),
["quality"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 0.25, 1.75),
["fertility"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 0.25, 1.75),
["health"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 0.25, 1.75)
}
if animalTypeIndex == AnimalType.COW or animalTypeIndex == AnimalType.SHEEP or animalTypeIndex == AnimalType.CHICKEN then genetics.productivity = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 0.25, 1.75) end
local name
if math.random() >= 0.85 then name = g_currentMission.animalNameSystem:getRandomName(animalGender) end
local animal = Animal.new(age, math.clamp((math.random(650, 1000) / 10) * genetics.health, 0, 100), monthsSinceLastBirth, animalGender, subTypeIndex, 0, isParent, isPregnant, animalTypeIndex == AnimalType.COW and animalGender == "female" and isParent and monthsSinceLastBirth < 10, nil, nil, nil, nil, nil, name, nil, nil, nil, nil, nil, genetics)
animal.farmId = tostring(farmId)
animal.uniqueId = uniqueId
animal.birthday.country = farmCountryIndex
local variations = self:getVisualByAge(subTypeIndex, age).visualAnimal.variations
local variationIndex = 1
if #variations > 1 then variationIndex = math.random(1, #variations) end
animal.variation = variationIndex
local environment = g_currentMission.environment
local month = environment.currentPeriod + 2
if month > 12 then month = month - 12 end
local day = 1 + math.floor((environment.currentDayInPeriod - 1) * (RealisticLivestock.DAYS_PER_MONTH[month] / environment.daysPerPeriod))
local year = environment.currentYear
animal.diseases = {}
g_diseaseManager:onDayChanged(animal)
g_diseaseManager:setGeneticDiseasesForSaleAnimal(animal)
if isPregnant then
local childNum = animal:generateRandomOffspring()
local children = {}
local minMetabolism, maxMetabolism = genetics.metabolism * 0.9, genetics.metabolism * 1.1
local minMeat, maxMeat = genetics.quality * 0.9, genetics.quality * 1.1
local minHealth, maxHealth = genetics.health * 0.9, genetics.health * 1.1
local minFertility, maxFertility = genetics.fertility * 0.9, genetics.fertility * 1.1
local minProductivity, maxProductivity
if genetics.productivity ~= nil then minProductivity, maxProductivity = genetics.productivity * 0.9, genetics.productivity * 1.1 end
for i = 1, childNum do
local gender = math.random() >= 0.5 and "male" or "female"
local childSubTypeIndex = subTypeIndex + (gender == "male" and 1 or 0)
local child = Animal.new(-1, 100, 0, gender, childSubTypeIndex, 0, false, false, false, nil, nil, animal.farmId .. " " .. animal.uniqueId)
local metabolism = math.random(minMetabolism * 100, maxMetabolism * 100) / 100
local quality = math.random(minMeat * 100, maxMeat * 100) / 100
local healthGenetics = math.random(minHealth * 100, maxHealth * 100) / 100
local fertility = math.random(minFertility * 100, maxFertility * 100) / 100
local productivity = nil
if genetics.productivity ~= nil then productivity = math.clamp(math.random(minProductivity * 100, maxProductivity * 100) / 100, 0.25, 1.75) end
child:setGenetics({
["metabolism"] = math.clamp(metabolism, 0.25, 1.75),
["quality"] = math.clamp(quality, 0.25, 1.75),
["health"] = math.clamp(healthGenetics, 0.25, 1.75),
["fertility"] = math.clamp(fertility, 0.25, 1.75),
["productivity"] = productivity
})
for _, disease in pairs(animal.diseases) do
disease:affectReproduction(child)
end
table.insert(children, child)
end
local reproductionDuration = subType.reproductionDurationMonth
if math.random() >= 0.99 then
if math.random() >= 0.95 then
reproductionDuration = reproductionDuration + math.random() >= 0.75 and -2 or 2
else
reproductionDuration = reproductionDuration + math.random() >= 0.85 and -1 or 1
end
reproductionDuration = math.max(reproductionDuration, 2)
end
local expectedYear = year + math.floor(reproductionDuration / 12)
local expectedMonth = month + (reproductionDuration % 12)
while expectedMonth > 12 do
expectedMonth = expectedMonth - 12
expectedYear = expectedYear + 1
end
local expectedDay = math.random(1, RealisticLivestock.DAYS_PER_MONTH[expectedMonth])
if #children > 0 then
animal.pregnancy = {
["duration"] = reproductionDuration,
["expected"] = {
["day"] = expectedDay,
["month"] = expectedMonth,
["year"] = expectedYear
},
["pregnancies"] = children
}
end
end
animal.sale = {
--["day"] = day,
--["month"] = month,
--["year"] = year
["day"] = environment.currentMonotonicDay
}
if animal.reproduction > 0 and (animal.pregnancy == nil or #animal.pregnancy.pregnancies == 0) then
animal.reproduction = 0
animal.pregnancy = nil
end
return animal
end
function AnimalSystem:getSaleAnimalsByTypeIndex(animalTypeIndex)
return self.animals[animalTypeIndex] or {}
end
function AnimalSystem:getAIAnimalsByTypeIndex(animalTypeIndex)
return self.aiAnimals[animalTypeIndex] or {}
end
function AnimalSystem:getFarmQuality(country, farmId)
if self.countries[country] ~= nil then
local farms = self.countries[country].farms
if type(farmId) == "string" then farmId = tonumber(farmId) end
for _, farm in pairs(farms) do
if farm.id == farmId then return farm.quality end
end
end
return 1
end
function AnimalSystem:getFarmSemenPrice(country, farmId)
if self.countries[country] ~= nil then
local farms = self.countries[country].farms
if type(farmId) == "string" then farmId = tonumber(farmId) end
for _, farm in pairs(farms) do
if farm.id == farmId then return farm.semenPrice end
end
end
return 1
end
function AnimalSystem:getNextAnimalIdForFarm(countryIndex, animalTypeIndex, farmId)
local country = self.countries[countryIndex]
if country == nil then return 1 end
local farms = country.farms
if type(farmId) == "string" then farmId = tonumber(farmId) end
for _, farm in pairs(farms) do
if farm.id == farmId then
--local index = "cowId"
--if animalTypeIndex == AnimalType.PIG then
-- index = "pigId"
--elseif animalTypeIndex == AnimalType.SHEEP then
-- index = "sheepId"
--elseif animalTypeIndex == AnimalType.CHICKEN then
-- index = "chickenId"
--elseif animalTypeIndex == AnimalType.HORSE then
-- index = "horseId"
--end
--if farm[index] ~= nil then
-- farm[index] = farm[index] + 1
-- return farm[index]
--end
if farm.ids[animalTypeIndex] ~= nil then
farm.ids[animalTypeIndex] = farm.ids[animalTypeIndex] + 1
return farm.ids[animalTypeIndex]
end
return 1
end
end
return 1
end
function AnimalSystem:removeSaleAnimal(animalTypeIndex, countryIndex, farmId, uniqueId)
for i, animal in pairs(self.animals[animalTypeIndex]) do
if animal.birthday.country == countryIndex and animal.farmId == farmId and animal.uniqueId == uniqueId then
table.remove(self.animals[animalTypeIndex], i)
return
end
end
end
function AnimalSystem:removeAIAnimal(animalTypeIndex, countryIndex, farmId, uniqueId)
for i, animal in pairs(self.aiAnimals[animalTypeIndex]) do
if animal.birthday.country == countryIndex and animal.farmId == farmId and animal.uniqueId == uniqueId then
table.remove(self.aiAnimals[animalTypeIndex], i)
return
end
end
end
function AnimalSystem:onHourChanged()
local day = g_currentMission.environment.currentMonotonicDay
local hasChanges = false
for animalTypeIndex, animals in pairs(self.animals) do
local indexesToRemove = {}
for i, animal in pairs(animals) do
if animal.sale ~= nil then
local saleDay = animal.sale.day
if saleDay == day then continue end
local geneticQuality = 0
local totalGenetics = 0
for _, value in pairs(animal.genetics) do
if value ~= nil then
totalGenetics = totalGenetics + 1
geneticQuality = geneticQuality + value
end
end
local averageGenetics = geneticQuality / totalGenetics
if math.random() >= (saleDay / day) / (averageGenetics * 1.45) then
table.insert(indexesToRemove, i)
hasChanges = true
end
end
end
for i = #indexesToRemove, 1, -1 do
table.remove(animals, indexesToRemove[i])
end
local threshold = math.random(10, self.maxDealerAnimals)
if #animals < threshold then
for i = #animals + 1, threshold do
local animal = self:createNewSaleAnimal(animalTypeIndex)
if animal ~= nil then
table.insert(animals, animal)
hasChanges = true
end
end
end
end
for animalTypeIndex, animals in pairs(self.aiAnimals) do
if #animals < 15 then
for i = #animals + 1, 15 do
local animal = self:createNewAIAnimal(animalTypeIndex)
if animal ~= nil then
table.insert(animals, animal)
hasChanges = true
end
end
end
end
if hasChanges then g_server:broadcastEvent(AnimalSystemStateEvent.new(self.countries, self.animals, self.aiAnimals)) end
end
function AnimalSystem:onDayChanged()
local environment = g_currentMission.environment
local month = environment.currentPeriod + 2
local currentDayInPeriod = environment.currentDayInPeriod
if month > 12 then month = month - 12 end
local daysPerPeriod = environment.daysPerPeriod
local day = 1 + math.floor((currentDayInPeriod - 1) * (RealisticLivestock.DAYS_PER_MONTH[month] / daysPerPeriod))
local year = environment.currentYear
for _, animals in pairs(self.animals) do
for _, animal in pairs(animals) do
animal.reserved = false
animal:onDayChanged(nil, self.isServer, day, month, year, currentDayInPeriod, daysPerPeriod, true)
end
end
for _, animals in pairs(self.aiAnimals) do
for _, animal in pairs(animals) do animal:onDayChanged(nil, self.isServer, day, month, year, currentDayInPeriod, daysPerPeriod, true) end
end
end
function AnimalSystem:onPeriodChanged()
for _, animals in pairs(self.animals) do
for _, animal in pairs(animals) do
animal:onPeriodChanged()
end
end
for _, animals in pairs(self.aiAnimals) do
for _, animal in pairs(animals) do
animal:onPeriodChanged()
end
end
if self.isServer then
local monitorCosts = {}
for _, placeable in pairs(g_currentMission.husbandrySystem.placeables) do
local animals = placeable:getClusters()
local ownerFarmId = placeable:getOwnerFarmId()
for _, animal in pairs(animals) do
if not animal.monitor.active and not animal.monitor.removed then continue end
if animal.monitor.removed and not animal.monitor.active then
local visualData = self:getVisualByAge(animal.subTypeIndex, animal.age)
if visualData.monitor ~= nil and animal.idFull ~= nil and animal.idFull ~= "1-1" then
local sep = string.find(animal.idFull, "-")
local husbandry = tonumber(string.sub(animal.idFull, 1, sep - 1))
local animalId = tonumber(string.sub(animal.idFull, sep + 1))
if husbandry ~= 0 and animalId ~= 0 then
local rootNode = getAnimalRootNode(husbandry, animalId)
if rootNode ~= 0 then
local monitorNode = I3DUtil.indexToObject(rootNode, visualData.monitor)
if monitorNode ~= nil and monitorNode ~= 0 then setVisibility(monitorNode, false) end
end
end
end
animal.monitor.removed = false
end
if monitorCosts[ownerFarmId] == nil then monitorCosts[ownerFarmId] = 0 end
monitorCosts[ownerFarmId] = monitorCosts[ownerFarmId] + animal.monitor.fee
end
end
for ownerFarmId, cost in pairs(monitorCosts) do
local ownerFarm = g_farmManager:getFarmById(ownerFarmId)
g_currentMission:addMoneyChange(0 - cost, ownerFarmId, MoneyType.MONITOR_SUBSCRIPTIONS, true)
ownerFarm:changeBalance(0 - cost, MoneyType.MONITOR_SUBSCRIPTIONS)
end
end
end
function AnimalSystem:addExistingSaleAnimal(animal)
local animalTypeIndex = animal.animalTypeIndex or 0
if self.animals[animalTypeIndex] ~= nil then table.insert(self.animals[animalTypeIndex], animal) end
end
function AnimalSystem:removeAllSaleAnimals(animalTypeIndex)
if animalTypeIndex == nil then
for index, animals in pairs(self.animals) do self.animals[index] = {} end
elseif self.animals[animalTypeIndex] ~= nil then
self.animals[animalTypeIndex] = {}
end
end
function AnimalSystem.onSettingChanged(name, state)
g_currentMission.animalSystem[name] = state
end
function AnimalSystem.onClickResetDealer()
local animalSystem = g_currentMission.animalSystem
if not animalSystem.isServer then return end
animalSystem:removeAllSaleAnimals()
for animalTypeIndex, animals in pairs(animalSystem.animals) do
for i = 1, animalSystem.maxDealerAnimals do
local animal = animalSystem:createNewSaleAnimal(animalTypeIndex)
if animal ~= nil then table.insert(animals, animal) end
end
animalSystem.animals[animalTypeIndex] = animals
end
end
function AnimalSystem:getBreedsByAnimalTypeIndex(animalTypeIndex)
return self.types[animalTypeIndex].breeds
end
function AnimalSystem:createNewAIAnimal(animalTypeIndex)
local animalType = self:getTypeByIndex(animalTypeIndex)
if animalType == nil then return nil end
local validSubTypes = {}
for _, subTypeIndex in pairs(animalType.subTypes) do
local subType = self:getSubTypeByIndex(subTypeIndex)
if subType.gender == "male" then table.insert(validSubTypes, subType) end
end
if #validSubTypes == 0 then return nil end
local subType = validSubTypes[math.random(1, #validSubTypes)]
if subType == nil then return end
local subTypeIndex = subType.subTypeIndex
local farmId, farmQuality, farmCountryIndex, lastAnimalId
local attemptedCountryIndexes = {}
while farmId == nil do
if #attemptedCountryIndexes == #self.countries then return nil end
local countryIndex
if #attemptedCountryIndexes == 0 and math.random() >= 0.12 then
countryIndex = RealisticLivestock.getMapCountryIndex()
else
countryIndex = math.random(1, #self.countries)
while table.find(attemptedCountryIndexes, countryIndex) ~= nil do
countryIndex = math.random(1, #self.countries)
end
end
table.insert(attemptedCountryIndexes, countryIndex)
local country = self.countries[countryIndex]
local validFarms = {}
for i = 1, #country.farms do
local farm = country.farms[i]
if farm.ids[animalTypeIndex] ~= nil and farm.quality >= 1.35 then table.insert(validFarms, i) end
end
if #validFarms == 0 then continue end
local farmIndex = validFarms[math.random(1, #validFarms)]
local farm = country.farms[farmIndex]
farmId = farm.id
farmQuality = farm.quality
farmCountryIndex = countryIndex
farm.ids[animalTypeIndex] = (farm.ids[animalTypeIndex] or 0) + 1
lastAnimalId = farm.ids[animalTypeIndex]
end
local age = math.random(subType.reproductionMinAgeMonth, subType.reproductionMinAgeMonth * 3)
local uniqueId = tostring(lastAnimalId)
local idLen = string.len(uniqueId)
if idLen < 5 then
if idLen == 1 then
uniqueId = "1000" .. uniqueId
elseif idLen == 2 then
uniqueId = "100" .. uniqueId
elseif idLen == 3 then
uniqueId = "10" .. uniqueId
elseif idLen == 4 then
uniqueId = "1" .. uniqueId
end
end
local concatenatedId = farmId .. uniqueId
local checkDigit = (tonumber(concatenatedId)::number % 7) + 1
uniqueId = checkDigit .. uniqueId
local geneticsModifier = farmQuality * 1000
local genetics = {
["metabolism"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 1.15, 1.75),
["quality"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 1.15, 1.75),
["fertility"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 1.15, 1.75),
["health"] = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 1.15, 1.75)
}
if animalTypeIndex == AnimalType.COW or animalTypeIndex == AnimalType.SHEEP or animalTypeIndex == AnimalType.CHICKEN then genetics.productivity = math.clamp(math.random(geneticsModifier - 300, geneticsModifier + 300) / 1000, 1.15, 1.75) end
local name = g_currentMission.animalNameSystem:getRandomName("male")
local animal = Animal.new(age, math.clamp((math.random(650, 1000) / 10) * genetics.health, 75, 100), 0, "male", subTypeIndex, 0, false, false, false, nil, nil, nil, nil, nil, name, nil, nil, nil, nil, nil, genetics)
animal.farmId = tostring(farmId)
animal.uniqueId = uniqueId
animal.birthday.country = farmCountryIndex
local variations = self:getVisualByAge(subTypeIndex, age).visualAnimal.variations
local variationIndex = 1
if #variations > 1 then variationIndex = math.random(1, #variations) end
animal.variation = variationIndex
animal.favouritedBy = {}
animal.success = math.clamp((math.random(35, 50) * genetics.fertility) / 100, 0.5, 1)
animal.isAIAnimal = true
return animal
end
================================================
FILE: src/animals/husbandry/RealisticLivestock_HusbandrySystem.lua
================================================
RealisticLivestock_HusbandrySystem = {}
function RealisticLivestock_HusbandrySystem:getClusterHusbandryById(superFunc, id)
for _, clusterHusbandry in ipairs(self.clusterHusbandries) do
if clusterHusbandry.husbandryIds ~= nil then
for _, husbandryId in ipairs(clusterHusbandry.husbandryIds) do
if husbandryId == id then return clusterHusbandry end
end
end
end
return nil
end
HusbandrySystem.getClusterHusbandryById = Utils.overwrittenFunction(HusbandrySystem.getClusterHusbandryById, RealisticLivestock_HusbandrySystem.getClusterHusbandryById)
================================================
FILE: src/animals/husbandry/cluster/RealisticLivestock_AnimalCluster.lua
================================================
RealisticLivestock_AnimalCluster = {}
function RealisticLivestock_AnimalCluster:saveToXMLFile(xmlFile, key, _)
if self.monthsSinceLastBirth == nil then self.monthsSinceLastBirth = 0 end
if self.lactatingAnimals == nil then self.lactatingAnimals = 0 end
if self.isParent == nil then self.isParent = false end
if self.gender == nil then self.gender = "female" end
xmlFile:setInt(key .. "#monthsSinceLastBirth", self.monthsSinceLastBirth)
xmlFile:setInt(key .. "#lactatingAnimals", self.lactatingAnimals)
xmlFile:setBool(key .. "#isParent", self.isParent)
xmlFile:setString(key .. "#gender", self.gender)
end
AnimalCluster.saveToXMLFile = Utils.appendedFunction(AnimalCluster.saveToXMLFile, RealisticLivestock_AnimalCluster.saveToXMLFile)
function RealisticLivestock_AnimalCluster:loadFromXMLFile(superFunc, xmlFile, key)
local r = superFunc(self, xmlFile, key)
self.isParent = xmlFile:getBool(key .. "#isParent")
self.monthsSinceLastBirth = xmlFile:getInt(key .. "#monthsSinceLastBirth")
self.lactatingAnimals = xmlFile:getInt(key .. "#lactatingAnimals")
self.gender = xmlFile:getString(key .. "#gender")
-- why is the age of animals clamped between 0 and 60 months?
self.age = xmlFile:getInt(key .. "#age")
if self.monthsSinceLastBirth == nil then
self.monthsSinceLastBirth = 0
end
if self.lactatingAnimals == nil then
self.lactatingAnimals = 0
end
if self.isParent == nil then
self.isParent = false
end
if self.gender == nil and self.subType ~= nil and self.subType == "CHICKEN_ROOSTER" then
self.gender = "male"
elseif self.gender == nil then
self.gender = "female"
end
return r
end
AnimalCluster.loadFromXMLFile = Utils.overwrittenFunction(AnimalCluster.loadFromXMLFile, RealisticLivestock_AnimalCluster.loadFromXMLFile)
function RealisticLivestock_AnimalCluster:showInfo(superFunc, box)
local index = self:getSubTypeIndex()
local subType = g_currentMission.animalSystem:getSubTypeByIndex(index)
local name = subType.name
local fillTypeTitle = g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)
box:addLine(g_i18n:getText("infohud_type"), fillTypeTitle)
box:addLine(g_i18n:getText("infohud_age"), g_i18n:formatNumMonth(self.age))
if self.numAnimals > 1 then
box:addLine(g_i18n:getText("infohud_numAnimals"), tostring(self.numAnimals))
end
box:addLine(g_i18n:getText("infohud_health"), string.format("%d %%", self.health))
if self.clusterSystem.owner.spec_husbandryMilk ~= nil and self.gender ~= nil and self.gender == "female" and self.age >= 12 then
local lactatingAnimals = self.lactatingAnimals
if lactatingAnimals ~= nil then box:addLine("Lactating animals", string.format("%d", lactatingAnimals)) end
end
if self.gender ~= nil and self.gender == "female" and subType.supportsReproduction then
box:addLine(g_i18n:getText("infohud_reproduction"), string.format("%d %%", self.reproduction))
local healthFactor = self:getHealthFactor()
local text = "Yes"
if self.age < subType.reproductionMinAgeMonth then
text = "No (too young)"
elseif not RealisticLivestock.hasMaleAnimalInPen(self.clusterSystem.owner.spec_husbandryAnimals, name) and self.reproduction < 100 / subType.reproductionDurationMonth then
text = "No (no suitable male animal)"
elseif healthFactor < subType.reproductionMinHealth then
text = "No (too unhealthy)"
end
box:addLine("Can reproduce", text)
end
end
AnimalCluster.showInfo = Utils.overwrittenFunction(AnimalCluster.showInfo, RealisticLivestock_AnimalCluster.showInfo)
function RealisticLivestock_AnimalCluster:addInfos(infos)
if self.gender ~= nil and self.gender == "female" and self.lactatingAnimals ~= nil and self.age > 12 and self.clusterSystem.owner.spec_husbandryMilk ~= nil then
if self.infoLactation == nil then
self.infoLactation = {
text = "Lactating animals",
title = "Lactating animals"
}
end
self.infoLactation.value = self.lactatingAnimals
self.infoLactation.ratio = self.lactatingAnimals / self.numAnimals
self.infoLactation.valueText = string.format("%d", self.lactatingAnimals)
table.insert(infos, self.infoLactation)
end
end
AnimalCluster.addInfos = Utils.appendedFunction(AnimalCluster.addInfos, RealisticLivestock_AnimalCluster.addInfos)
function RealisticLivestock_AnimalCluster:changeAge(superFunc, delta)
-- whats even the point of having an aging system if animals dont age past 5 years old? animals die in real life, i get that you want your "E - Everyone" rating, but its not like 3 year olds are playing this £50 game. realistically your main audience is adult men, even moreso at this expensive price.
self.age = self.age + delta
end
AnimalCluster.changeAge = Utils.overwrittenFunction(AnimalCluster.changeAge, RealisticLivestock_AnimalCluster.changeAge)
================================================
FILE: src/animals/husbandry/cluster/RealisticLivestock_AnimalClusterHusbandry.lua
================================================
RealisticLivestock_AnimalClusterHusbandry = {}
RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES = 50
local modDirectory = g_currentModDirectory
function RealisticLivestock_AnimalClusterHusbandry:create(superFunc, xmlFilename, navigationNode, raycastDistance, collisionMask)
if self.husbandryId ~= nil then
self:deleteHusbandry()
end
self.navigationNode = navigationNode
self.collisionMask = collisionMask
self.xmlFilename = xmlFilename
self.raycastDistance = raycastDistance
self.visualAnimalCount = 0
local animalPositioning = CollisionMask.ANIMAL_POSITIONING
self.husbandryIds = {}
self.husbandryIdsToVisualAnimalCount = {}
for i=1, 8 do
local husbandry = createAnimalHusbandry(self.animalTypeName, navigationNode, xmlFilename, raycastDistance, animalPositioning, collisionMask, AudioGroup.ENVIRONMENT)
if husbandry == 0 then
Logging.error("Failed to create animal husbandry for %q with navigation mesh %q and config %q", self.animalTypeName, I3DUtil.getNodePath(navigationNode), xmlFilename)
break
end
table.insert(self.husbandryIds, husbandry)
self.husbandryIdsToVisualAnimalCount[husbandry] = 0
end
self.husbandryId = self.husbandryIds[1]
self.visualUpdatePending = true
self:onIndoorStateChanged()
return self.husbandryId
end
AnimalClusterHusbandry.create = Utils.overwrittenFunction(AnimalClusterHusbandry.create, RealisticLivestock_AnimalClusterHusbandry.create)
function RealisticLivestock_AnimalClusterHusbandry:deleteHusbandry(superFunc)
if self.husbandryIds ~= nil then
if self.animalIdToCluster == nil then self.animalIdToCluster = {} end
for husbandryId, animalIds in pairs(self.animalIdToCluster) do
for animalId, animal in pairs(animalIds) do
removeHusbandryAnimal(self.husbandryIds[husbandryId], animalId)
animal:deleteVisual()
end
end
g_soundManager:removeIndoorStateChangedListener(self)
for _, id in pairs(self.husbandryIds) do
delete(id)
end
self.husbandryIds = nil
self.husbandryId = nil
self.husbandryIdsToVisualAnimalCount = nil
self.animalIdToCluster = nil
end
end
AnimalClusterHusbandry.deleteHusbandry = Utils.overwrittenFunction(AnimalClusterHusbandry.deleteHusbandry, RealisticLivestock_AnimalClusterHusbandry.deleteHusbandry)
function RealisticLivestock_AnimalClusterHusbandry:updateVisuals(superFunc, removeAll)
if self.husbandryId == nil or not isHusbandryReady(self.husbandryId) then
self.visualUpdatePending = true
return
end
local animals = self.nextUpdateClusters or {}
self.totalNumAnimalsPerVisualAnimalIndex = {}
local newAnimalMapping = {}
local newAnimalIdToVisualAnimalIndex = {}
if self.animalIdToCluster == nil then self.animalIdToCluster = {} end
for husbandryId, animalIds in pairs(self.animalIdToCluster) do
if type(animalIds) ~= "table" then continue end
local idsToRemove = {}
for animalId, animal in pairs(animalIds) do
if removeAll or animal == nil or animal.isSold or animal.isDead or animal.id == nil or animal.uniqueId == "1-1" or animal.uniqueId == "0-0" or animal.numAnimals <= 0 then
self.husbandryIdsToVisualAnimalCount[self.husbandryIds[husbandryId]] = math.max(self.husbandryIdsToVisualAnimalCount[self.husbandryIds[husbandryId]] - 1, 0)
self.visualAnimalCount = math.max(self.visualAnimalCount - 1, 0)
removeHusbandryAnimal(self.husbandryIds[husbandryId], animalId)
if animal ~= nil then
animal.id = nil
animal.idFull = nil
animal:deleteVisual()
end
table.insert(idsToRemove, animalId)
end
end
for _, animalId in pairs(idsToRemove) do
animalIds[animalId] = nil
end
end
if removeAll then self.animalIdToCluster = {} end
if RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES <= 0 or self.visualAnimalCount == RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES then return end
local areaCode = RealisticLivestock.getMapCountryCode()
local i = 1
local profile = Utils.getPerformanceClassId()
local maxAnimalsPerHusbandry = (profile == GS_PROFILE_VERY_LOW and 8) or (profile == GS_PROFILE_LOW and 10) or (profile == GS_PROFILE_MEDIUM and 16) or (profile == GS_PROFILE_HIGH and 20) or (profile == GS_PROFILE_VERY_HIGH and 25) or (profile == GS_PROFILE_ULTRA and 25) or 8
local animalSystem = g_currentMission.animalSystem
local animalType = animalSystem.types[self.placeable:getAnimalTypeIndex()]
local colours = animalType.colours or animalSystem.baseColours
if colours.earTagLeft == nil or colours.earTagLeft_text == nil or colours.earTagRight == nil or colours.earTagRight_text == nil then colours = animalSystem.baseColours end
local earTagLeftR, earTagLeftG, earTagLeftB = colours.earTagLeft[1], colours.earTagLeft[2], colours.earTagLeft[3]
local earTagLeftTextR, earTagLeftTextG, earTagLeftTextB = colours.earTagLeft_text[1], colours.earTagLeft_text[2], colours.earTagLeft_text[3]
local earTagRightR, earTagRightG, earTagRightB = colours.earTagRight[1], colours.earTagRight[2], colours.earTagRight[3]
local earTagRightTextR, earTagRightTextG, earTagRightTextB = colours.earTagRight_text[1], colours.earTagRight_text[2], colours.earTagRight_text[3]
for _, animal in pairs(animals) do
if self.visualAnimalCount >= RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES or i > #self.husbandryIds or animal.isDead or animal.numAnimals <= 0 or animal.uniqueId == "1-1" or animal.uniqueId == "0-0" or (animal.id ~= nil and animal.idFull ~= nil and animal.id ~= "0-0" and animal.visualAnimalIndex == nil) then continue end
local husbandryAnimalCount = self.husbandryIdsToVisualAnimalCount[self.husbandryIds[i]]
local useTempId = false
local tempHusbandryId
local animalId = 0
if animal.id ~= nil and animal.idFull ~= nil and animal.id ~= "0-0" and animal.visualAnimalIndex ~= nil then
local age = animal:getAge()
local newVisualAnimalIndex = self.animalSystem:getVisualAnimalIndexByAge(animal:getSubTypeIndex(), age == -1 and 0 or age)
if newVisualAnimalIndex ~= animal.visualAnimalIndex then
tempHusbandryId = tonumber(string.sub(animal.id, 1, 1))
local tempAnimalId = tonumber(string.sub(animal.id, 3))
removeHusbandryAnimal(self.husbandryIds[tempHusbandryId], tempAnimalId)
animal:deleteVisual()
animalId = addHusbandryAnimal(self.husbandryIds[tempHusbandryId], newVisualAnimalIndex - 1)
self.visualAnimalCount = math.max(self.visualAnimalCount - 1, 0)
husbandryAnimalCount = husbandryAnimalCount - 1
self.animalIdToCluster[tempHusbandryId][tempAnimalId] = nil
if animalId == nil then
self.husbandryIdsToVisualAnimalCount[self.husbandryIds[i]] = self.husbandryIdsToVisualAnimalCount[self.husbandryIds[i]] - 1
continue
end
useTempId = true
else
continue
end
end
local subTypeIndex = animal:getSubTypeIndex()
local age = animal:getAge()
age = age == -1 and 0 or age
local visualAnimalIndex = self.animalSystem:getVisualAnimalIndexByAge(subTypeIndex, age)
if animalId == 0 then
while not useTempId and husbandryAnimalCount >= maxAnimalsPerHusbandry and i <= #self.husbandryIds and self.visualAnimalCount < RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES do
i = i + 1
if i > #self.husbandryIds then break end
husbandryAnimalCount = self.husbandryIdsToVisualAnimalCount[self.husbandryIds[i]]
end
if i > #self.husbandryIds or (husbandryAnimalCount >= maxAnimalsPerHusbandry and not useTempId) then break end
animalId = addHusbandryAnimal(self.husbandryIds[useTempId and tempHusbandryId or i], visualAnimalIndex - 1)
while animalId == 0 and i <= #self.husbandryIds do
i = useTempId and i or (i + 1)
useTempId = false
if i > #self.husbandryIds or self.husbandryIdsToVisualAnimalCount[self.husbandryIds[i]] >= maxAnimalsPerHusbandry then break end
animalId = addHusbandryAnimal(self.husbandryIds[i], visualAnimalIndex - 1)
end
end
if animalId > 0 then
self.visualAnimalCount = self.visualAnimalCount + 1
husbandryAnimalCount = husbandryAnimalCount + 1
local visualData = self.animalSystem:getVisualByAge(subTypeIndex, age)
local variations = visualData.visualAnimal.variations
if #variations >= 1 then
local variationIndex = animal.variation
if variationIndex == nil or variationIndex > #variations then
variationIndex = math.random(1, #variations)
animal.variation = variationIndex
end
local variation = variations[variationIndex]
setAnimalTextureTile(self.husbandryIds[useTempId and tempHusbandryId or i], animalId, variation.tileUIndex, variation.tileVIndex)
end
if not self.animalIdToCluster[useTempId and tempHusbandryId or i] then
self.animalIdToCluster[useTempId and tempHusbandryId or i] = {}
end
animal.id = (useTempId and tempHusbandryId or i) .. "-" .. animalId
animal.idFull = self.husbandryIds[useTempId and tempHusbandryId or i] .. "-" .. animalId
animal.visualAnimalIndex = visualAnimalIndex
self.animalIdToCluster[useTempId and tempHusbandryId or i][animalId] = animal
animal:createVisual(self.husbandryIds[useTempId and tempHusbandryId or i], animalId)
animal:setVisualEarTagColours(colours.earTagLeft, colours.earTagLeft_text, colours.earTagRight, colours.earTagRight_text)
else
animal.id = nil
animal.idFull = nil
animal.visualAnimalIndex = nil
end
self.husbandryIdsToVisualAnimalCount[self.husbandryIds[i]] = husbandryAnimalCount
if self.visualAnimalCount > RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES then break end
if animalId == 0 then break end
end
--print(string.format("RealisticLivestock: %d visual animals loaded out of %d total animals for husbandry (%d max)", visualAnimalCount, #animals, RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES))
i = 1
for husbandryId, animalIds in pairs(self.animalIdToCluster) do
for animalId, animal in animalIds do
local dirtFactor = 0.1
local animalRootNode = getAnimalRootNode(self.husbandryIds[husbandryId], animalId)
if animalRootNode == 0 then break end
I3DUtil.setShaderParameterRec(animalRootNode, "dirt", dirtFactor, nil, nil, nil)
local x, y, z, w = getAnimalShaderParameter(self.husbandryIds[husbandryId], animalId, "atlasInvSizeAndOffsetUV")
I3DUtil.setShaderParameterRec(animalRootNode, "atlasInvSizeAndOffsetUV", x, y, z, w)
end
end
--self.animalIdToCluster = newAnimalMapping
self.animalIdToVisualAnimalIndex = newAnimalIdToVisualAnimalIndex
self:getPlaceable().spec_husbandryAnimals.clusterSystem:updateIdMapping()
self.nextUpdateClusters = nil
self.visualUpdatePending = false
end
AnimalClusterHusbandry.updateVisuals = Utils.overwrittenFunction(AnimalClusterHusbandry.updateVisuals, RealisticLivestock_AnimalClusterHusbandry.updateVisuals)
function RealisticLivestock_AnimalClusterHusbandry:getAnimalPosition(superFunc, id)
for husbandryId, animalIds in pairs(self.animalIdToCluster) do
for animalId, animal in pairs(animalIds) do
if animal.id == id or animal.farmId .. " " .. animal.uniqueId == id then
local x, y, z = getAnimalPosition(self.husbandryIds[husbandryId], animalId)
local a, b, c = getAnimalRotation(self.husbandryIds[husbandryId], animalId)
return x, y, z, a, b, c
end
end
end
return nil
end
AnimalClusterHusbandry.getAnimalPosition = Utils.overwrittenFunction(AnimalClusterHusbandry.getAnimalPosition, RealisticLivestock_AnimalClusterHusbandry.getAnimalPosition)
function RealisticLivestock_AnimalClusterHusbandry:getClusterByAnimalId(superFunc, id, husbandryId)
if husbandryId ~= nil then
for index, husbandryIdFull in ipairs(self.husbandryIds) do
if husbandryIdFull == husbandryId and self.animalIdToCluster[index] ~= nil and self.animalIdToCluster[index][id] ~= nil then return self.animalIdToCluster[index][id] end
end
return nil
end
if type(id) ~= "string" then id = tostring(id) end
if string.contains(id, "-") then
local a, _ = string.find(id, "-")
husbandryId = string.sub(id, 1, (a - 1) or 2)
local animalId = string.sub(id, (a + 1) or 1)
if husbandryId ~= nil and animalId ~= nil and self.animalIdToCluster[husbandryId] ~= nil and self.animalIdToCluster[husbandryId][animalId] ~= nil then return self.animalIdToCluster[husbandryId][animalId] end
end
for husbandryId, animalIds in pairs(self.animalIdToCluster) do
for animalId, animal in pairs(animalIds) do
if animal.id == id then
--return animal
end
end
end
return nil
end
AnimalClusterHusbandry.getClusterByAnimalId = Utils.overwrittenFunction(AnimalClusterHusbandry.getClusterByAnimalId, RealisticLivestock_AnimalClusterHusbandry.getClusterByAnimalId)
================================================
FILE: src/animals/husbandry/cluster/RealisticLivestock_AnimalClusterSystem.lua
================================================
RealisticLivestock_AnimalClusterSystem = {}
local AnimalClusterSystem_mt = Class(AnimalClusterSystem)
function RealisticLivestock_AnimalClusterSystem.new(superFunc, isServer, owner, customMt)
local self = setmetatable({}, customMt or AnimalClusterSystem_mt)
self.isServer = isServer
self.owner = owner
self.clusters = {}
self.idToIndex = {}
self.clustersToAdd = {}
self.clustersToRemove = {}
self.needsUpdate = false
self.animals = {}
self.currentAnimalId = 0
return self
end
AnimalClusterSystem.new = Utils.overwrittenFunction(AnimalClusterSystem.new, RealisticLivestock_AnimalClusterSystem.new)
function RealisticLivestock_AnimalClusterSystem:delete()
for _, animal in pairs(self.animals) do
animal:delete()
end
self.animals = {}
self.currentAnimalId = 0
end
AnimalClusterSystem.delete = Utils.appendedFunction(AnimalClusterSystem.delete, RealisticLivestock_AnimalClusterSystem.delete)
function RealisticLivestock_AnimalClusterSystem:getNextAnimalId()
self.currentAnimalId = self.currentAnimalId + 1
return self.currentAnimalId
end
AnimalClusterSystem.getNextAnimalId = RealisticLivestock_AnimalClusterSystem.getNextAnimalId
function RealisticLivestock_AnimalClusterSystem:getAnimals()
return self.animals or {}
end
AnimalClusterSystem.getAnimals = RealisticLivestock_AnimalClusterSystem.getAnimals
function RealisticLivestock_AnimalClusterSystem:loadFromXMLFile(_, xmlFile, key)
self.animals = {}
xmlFile:iterate(key .. ".RLAnimal", function(_, legacyKey)
local animal = Animal.loadFromXMLFile(xmlFile, legacyKey, self, true)
if animal ~= nil then table.insert(self.animals, animal) end
end)
xmlFile:iterate(key .. ".animal", function(_, animalKey)
local numAnimals = xmlFile:getInt(animalKey .. "#numAnimals", 1)
for i = 1, numAnimals do
local animal = Animal.loadFromXMLFile(xmlFile, animalKey, self)
if animal ~= nil then table.insert(self.animals, animal) end
end
end)
self:updateClusters()
self.needsUpdate = false
if self.owner ~= nil and self.owner.spec_husbandryFood ~= nil then SpecializationUtil.raiseEvent(self.owner, "onHusbandryAnimalsUpdate", self.animals) end
end
AnimalClusterSystem.loadFromXMLFile = Utils.overwrittenFunction(AnimalClusterSystem.loadFromXMLFile, RealisticLivestock_AnimalClusterSystem.loadFromXMLFile)
function RealisticLivestock_AnimalClusterSystem:saveToXMLFile(superFunc, xmlFile, key, usedModNames)
local toRemove = {}
for i, animal in pairs(self.animals) do
if animal == nil or animal.isDead or animal.isSold or animal.numAnimals <= 0 then table.insert(toRemove, i) end
end
for i=#toRemove, 1, -1 do
table.remove(self.animals, toRemove[i])
end
for i, animal in pairs(self.animals) do
--local animalKey = string.format("%s.animal(%d)", key, i - 1)
--local subType = g_currentMission.animalSystem:getSubTypeByIndex(cluster.subTypeIndex)
--xmlFile:setString(animalKey .. "#subType", subType.name)
--cluster:saveToXMLFile(xmlFile, animalKey, usedModNames)
local animalKey = string.format("%s.animal(%d)", key, i - 1)
animal:saveToXMLFile(xmlFile, animalKey)
end
end
AnimalClusterSystem.saveToXMLFile = Utils.overwrittenFunction(AnimalClusterSystem.saveToXMLFile, RealisticLivestock_AnimalClusterSystem.saveToXMLFile)
function RealisticLivestock_AnimalClusterSystem:readStream(_, streamId, connection)
local numAnimals = streamReadUInt16(streamId)
for i = 1, numAnimals do
local animalTypeIndex = streamReadUInt8(streamId)
local country = streamReadUInt8(streamId)
local uniqueId = streamReadString(streamId)
local farmId = streamReadString(streamId)
local existingAnimal = false
for _, animal in pairs(self.animals) do
if animal.birthday.country == country and animal.animalTypeIndex == animalTypeIndex and animal.uniqueId == uniqueId and animal.farmId == farmId then
animal:readStream(streamId, connection)
animal.foundThisUpdate = true
existingAnimal = true
break
end
end
if not existingAnimal then
local animal = Animal.new()
animal:readStream(streamId, connection)
animal.foundThisUpdate = true
self:addCluster(animal)
end
end
for i = #self.animals, 1, -1 do
local animal = self.animals[i]
if not animal.foundThisUpdate then
self:removeCluster(i)
else
animal.foundThisUpdate = false
end
end
self:updateIdMapping()
g_messageCenter:publish(AnimalClusterUpdateEvent, self.owner, self.animals)
end
AnimalClusterSystem.readStream = Utils.overwrittenFunction(AnimalClusterSystem.readStream, RealisticLivestock_AnimalClusterSystem.readStream)
function RealisticLivestock_AnimalClusterSystem:writeStream(_, streamId, connection)
streamWriteUInt16(streamId, #self.animals)
for _, animal in pairs(self.animals) do
streamWriteUInt8(streamId, animal.animalTypeIndex)
streamWriteUInt8(streamId, animal.birthday.country)
streamWriteString(streamId, animal.uniqueId)
streamWriteString(streamId, animal.farmId)
local success = animal:writeStream(streamId, connection)
end
end
AnimalClusterSystem.writeStream = Utils.overwrittenFunction(AnimalClusterSystem.writeStream, RealisticLivestock_AnimalClusterSystem.writeStream)
function RealisticLivestock_AnimalClusterSystem:getClusters(superFunc)
return self.animals or {}
end
AnimalClusterSystem.getClusters = Utils.overwrittenFunction(AnimalClusterSystem.getClusters, RealisticLivestock_AnimalClusterSystem.getClusters)
function RealisticLivestock_AnimalClusterSystem:getCluster(superFunc, index)
return self.animals[index] or nil
end
AnimalClusterSystem.getCluster = Utils.overwrittenFunction(AnimalClusterSystem.getCluster, RealisticLivestock_AnimalClusterSystem.getCluster)
function RealisticLivestock_AnimalClusterSystem:getClusterById(superFunc, id)
local index = self.idToIndex[id]
if id == nil or self.animals == nil then return end
if string.contains(id, "-") then
for _, animal in pairs(self.animals) do
if animal.id == id then return animal end
end
end
for _, animal in pairs(self.animals) do
if animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country == id then return animal end
end
if index == nil or self.animals == nil or self.animals[index] == nil then return nil end
return self.animals[index]
end
AnimalClusterSystem.getClusterById = Utils.overwrittenFunction(AnimalClusterSystem.getClusterById, RealisticLivestock_AnimalClusterSystem.getClusterById)
function RealisticLivestock_AnimalClusterSystem:addCluster(superFunc, animal)
if animal.uniqueId == nil or animal.uniqueId == "1-1" or animal.uniqueId == "0-0" then return end
animal:setClusterSystem(self)
table.insert(self.animals, animal)
self:updateIdMapping()
end
AnimalClusterSystem.addCluster = Utils.overwrittenFunction(AnimalClusterSystem.addCluster, RealisticLivestock_AnimalClusterSystem.addCluster)
function RealisticLivestock_AnimalClusterSystem:removeCluster(_, animalIndex)
if self.animals[animalIndex] ~= nil then
local animal = self.animals[animalIndex]
local spec = self.owner.spec_husbandryAnimals
if animal.idFull ~= nil and animal.idFull ~= "1-1" and spec ~= nil then
local sep = string.find(animal.idFull, "-")
local husbandry = tonumber(string.sub(animal.idFull, 1, sep - 1))
local animalId = tonumber(string.sub(animal.idFull, sep + 1))
if husbandry ~= 0 and animalId ~= 0 then
removeHusbandryAnimal(husbandry, animalId)
local clusterHusbandry = spec.clusterHusbandry
clusterHusbandry.husbandryIdsToVisualAnimalCount[husbandry] = math.max(clusterHusbandry.husbandryIdsToVisualAnimalCount[husbandry] - 1, 0)
clusterHusbandry.visualAnimalCount = math.max(clusterHusbandry.visualAnimalCount - 1, 0)
for husbandryIndex, animalIds in pairs(clusterHusbandry.animalIdToCluster) do
if clusterHusbandry.husbandryIds[husbandryIndex] == husbandry then
animalIds[animalId] = nil
break
end
end
end
end
table.remove(self.animals, animalIndex)
animal:setClusterSystem(nil)
else
for i, animal in pairs(self.animals) do
if animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country == animalIndex then
local spec = self.owner.spec_husbandryAnimals
if animal.idFull ~= nil and animal.idFull ~= "1-1" and spec ~= nil then
local sep = string.find(animal.idFull, "-")
local husbandry = tonumber(string.sub(animal.idFull, 1, sep - 1))
local animalId = tonumber(string.sub(animal.idFull, sep + 1))
if husbandry ~= 0 and animalId ~= 0 then
removeHusbandryAnimal(husbandry, animalId)
local clusterHusbandry = spec.clusterHusbandry
clusterHusbandry.husbandryIdsToVisualAnimalCount[husbandry] = math.max(clusterHusbandry.husbandryIdsToVisualAnimalCount[husbandry] - 1, 0)
clusterHusbandry.visualAnimalCount = math.max(clusterHusbandry.visualAnimalCount - 1, 0)
end
end
table.remove(self.animals, i)
animal:setClusterSystem(nil)
break
end
end
end
self:updateIdMapping()
end
AnimalClusterSystem.removeCluster = Utils.overwrittenFunction(AnimalClusterSystem.removeCluster, RealisticLivestock_AnimalClusterSystem.removeCluster)
function RealisticLivestock_AnimalClusterSystem:updateClusters(superFunc)
--assert(self.isServer, "AnimalClusterSystem:updateClusters is a server function")
local isDirty = false
local removedClusterIndices = {}
for animalsToAdd, pending in pairs(self.clustersToAdd) do
if not pending then continue end
if animalsToAdd.isIndividual ~= nil then
self:addCluster(animalsToAdd)
isDirty = true
continue
end
if animalsToAdd.numAnimals ~= nil then
local subType = g_currentMission.animalSystem:getSubTypeByIndex(animalsToAdd.subTypeIndex)
for i=1, animalsToAdd.numAnimals do
local genetics = animalsToAdd.genetics or nil
local impregnatedBy = animalsToAdd.impregnatedBy or nil
local animal = Animal.new(animalsToAdd.age, animalsToAdd.health, animalsToAdd.monthsSinceLastBirth or 0, subType.gender, animalsToAdd.subTypeIndex, animalsToAdd.reproduction or 0, animalsToAdd.isParent or false, animalsToAdd.isPregnant or false, animalsToAdd.isLactating or false, self, animalsToAdd.uniqueId, animalsToAdd.motherId, animalsToAdd.fatherId, nil, animalsToAdd.name, animalsToAdd.dirt, animalsToAdd.fitness, animalsToAdd.riding, animalsToAdd.farmId, animalsToAdd.weight, genetics, impregnatedBy, animalsToAdd.variation, animalsToAdd.children, animalsToAdd.monitor)
self:addCluster(animal)
isDirty = true
end
continue
end
for _, animalToAdd in pairs(animalsToAdd) do
if animalToAdd.isIndividual then
self:addCluster(animalToAdd)
isDirty = true
else
local subType = g_currentMission.animalSystem:getSubTypeByIndex(animalToAdd.subTypeIndex)
for i=1, animalToAdd.numAnimals do
local genetics = animalToAdd.genetics or nil
local impregnatedBy = animalToAdd.impregnatedBy or nil
local animal = Animal.new(animalToAdd.age, animalToAdd.health, animalToAdd.monthsSinceLastBirth or 0, subType.gender, animalToAdd.subTypeIndex, animalToAdd.reproduction or 0, animalToAdd.isParent or false, animalToAdd.isPregnant or false, animalToAdd.isLactating or false, self, animalToAdd.uniqueId, animalToAdd.motherId, animalToAdd.fatherId, nil, animalToAdd.name, animalToAdd.dirt, animalToAdd.fitness, animalToAdd.riding, animalToAdd.farmId, animalToAdd.weight, genetics, impregnatedBy, animalToAdd.variation, animalToAdd.children, animalToAdd.monitor)
self:addCluster(animal)
isDirty = true
end
end
end
end
for animalIndex, animal in pairs(self.animals) do
if animal.isDirty then
isDirty = true
animal.isDirty = false
end
--if animal:getNumAnimals() <= 0 and not animal.isDead and not animal.isSold then animal.numAnimals = 1 end
if self.clustersToRemove[animal] ~= nil or (animal.beingRidden ~= nil and animal.beingRidden) or animal:getNumAnimals() == 0 or animal.uniqueId == "1-1" or animal.uniqueId == "0-0" then table.insert(removedClusterIndices, animalIndex) end
end
for i = #removedClusterIndices, 1, -1 do
isDirty = true
local animalIndexToRemove = removedClusterIndices[i]
self:removeCluster(animalIndexToRemove)
end
--if isDirty then
-- g_server:broadcastEvent(AnimalClusterUpdateEvent.new(self.owner, self.animals), true)
--g_messageCenter:publish(AnimalClusterUpdateEvent, self.owner, self.animals)
--end
self.clustersToAdd = {}
self.clustersToRemove = {}
self:updateIdMapping()
if self.owner.spec_husbandryAnimals ~= nil then self.owner.spec_husbandryAnimals:updateVisualAnimals() end
end
AnimalClusterSystem.updateClusters = Utils.overwrittenFunction(AnimalClusterSystem.updateClusters, RealisticLivestock_AnimalClusterSystem.updateClusters)
function RealisticLivestock_AnimalClusterSystem:updateIdMapping(superFunc)
self.idToIndex = {}
for index, animal in pairs(self.animals) do
if index == nil then continue end
self.idToIndex[animal.farmId .. " " .. animal.uniqueId] = index
end
if self.owner.updatedClusters ~= nil then self.owner:updatedClusters(self.owner, self.animals) end
if g_server ~= nil then g_server:broadcastEvent(AnimalClusterUpdateEvent.new(self.owner, self.animals)) end
g_messageCenter:publish(AnimalClusterUpdateEvent, self.owner, self.animals)
end
AnimalClusterSystem.updateIdMapping = Utils.overwrittenFunction(AnimalClusterSystem.updateIdMapping, RealisticLivestock_AnimalClusterSystem.updateIdMapping)
================================================
FILE: src/animals/husbandry/cluster/VisualAnimal.lua
================================================
VisualAnimal = {}
local VisualAnimal_mt = Class(VisualAnimal)
function VisualAnimal.new(animal, husbandryId, animalId)
local self = setmetatable({}, VisualAnimal_mt)
self.animal = animal
self.husbandryId = husbandryId
self.animalId = animalId
self.nodes = {
["root"] = getAnimalRootNode(husbandryId, animalId)
}
self.texts = {
["earTagLeft"] = {},
["earTagRight"] = {}
}
self.leftTextColour, self.rightTextColour = { 0, 0, 0 }, { 0, 0, 0 }
return self
end
function VisualAnimal:delete()
for _, nodeType in pairs(self.texts) do
for _, nodes in pairs(nodeType) do
for _, node in pairs(nodes) do delete3DLinkedText(node) end
end
end
end
function VisualAnimal:load()
local nodes = self.nodes
local visualData = g_currentMission.animalSystem:getVisualByAge(self.animal.subTypeIndex, self.animal.age)
if visualData.monitor ~= nil then nodes.monitor = I3DUtil.indexToObject(nodes.root, visualData.monitor) end
if visualData.noseRing ~= nil then nodes.noseRing = I3DUtil.indexToObject(nodes.root, visualData.noseRing) end
if visualData.bumId ~= nil then nodes.bumId = I3DUtil.indexToObject(nodes.root, visualData.bumId) end
if visualData.marker ~= nil then nodes.marker = I3DUtil.indexToObject(nodes.root, visualData.marker) end
if visualData.earTagLeft ~= nil then nodes.earTagLeft = I3DUtil.indexToObject(nodes.root, visualData.earTagLeft) end
if visualData.earTagRight ~= nil then nodes.earTagRight = I3DUtil.indexToObject(nodes.root, visualData.earTagRight) end
self:setMonitor()
self:setNoseRing()
self:setBumId()
self:setMarker()
self:setLeftEarTag()
self:setRightEarTag()
end
function VisualAnimal:setMonitor()
if self.nodes.monitor == nil then return end
setVisibility(self.nodes.monitor, self.animal.monitor.active or self.animal.monitor.removed)
end
function VisualAnimal:setNoseRing()
if self.nodes.noseRing == nil then return end
setVisibility(self.nodes.noseRing, self.animal.gender == "male")
end
function VisualAnimal:setBumId()
if self.nodes.bumId == nil then return end
local uniqueId = self.animal.uniqueId
self.texts.bumId = {
["uniqueId"] = {
["top"] = create3DLinkedText(self.nodes.bumId, 0, -0.006, 0, 0, 0, 0, 0.05, string.sub(uniqueId, 3, 4)),
["bottom"] = create3DLinkedText(self.nodes.bumId, 0, -0.012, 0, 0, 0, 0, 0.05, string.sub(uniqueId, 5, 6))
}
}
end
function VisualAnimal:setMarker()
if self.nodes.marker == nil then return end
local markerColour = AnimalSystem.BREED_TO_MARKER_COLOUR[self.animal.breed]
local isMarked = self.animal:getMarked()
setVisibility(self.nodes.marker, isMarked)
if isMarked then setShaderParameter(self.nodes.marker, "colorScale", markerColour[1], markerColour[2], markerColour[3], nil, false) end
end
function VisualAnimal:setEarTagColours(leftTag, leftText, rightTag, rightText)
if self.nodes.earTagLeft ~= nil then
if leftTag ~= nil then setShaderParameter(self.nodes.earTagLeft, "colorScale", leftTag[1], leftTag[2], leftTag[3], nil, false) end
if leftText ~= nil then
self.leftTextColour = leftText
for _, nodes in pairs(self.texts.earTagLeft) do
for _, node in pairs(nodes) do change3DLinkedTextColour(node, leftText[1], leftText[2], leftText[3], 1) end
end
end
end
if self.nodes.earTagRight ~= nil then
if rightTag ~= nil then setShaderParameter(self.nodes.earTagRight, "colorScale", rightTag[1], rightTag[2], rightTag[3], nil, false) end
if rightText ~= nil then
self.rightTextColour = rightText
for _, nodes in pairs(self.texts.earTagRight) do
for _, node in pairs(nodes) do change3DLinkedTextColour(node, rightText[1], rightText[2], rightText[3], 1) end
end
end
end
end
function VisualAnimal:setLeftEarTag()
if self.nodes.earTagLeft == nil then return end
for _, nodes in pairs(self.texts.earTagLeft) do
for _, node in pairs(nodes) do delete3DLinkedText(node) end
end
local uniqueId = self.animal.uniqueId
local farmId = self.animal.farmId
local birthday = self.animal:getBirthday()
local countryCode = birthday ~= nil and birthday.country ~= nil and (RealisticLivestock.AREA_CODES[birthday.country] or RealisticLivestock.getMapCountryCode()).code
local node = self.nodes.earTagLeft
local colour = self.leftTextColour
local front = getChild(node, "front")
local back = getChild(node, "back")
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_MIDDLE)
setTextAlignment(RenderText.ALIGN_CENTER)
setTextColor(colour[1], colour[2], colour[3], 1)
setTextFont(RealisticLivestock.FONTS.dejavu_sans)
self.texts.earTagLeft = {
["uniqueId"] = {
["back"] = create3DLinkedText(back, 0, -0.006, -0.015, 0, 0, 0, 0.035, uniqueId),
["front"] = create3DLinkedText(front, 0, -0.006, -0.015, 0, 0, 0, 0.035, uniqueId)
},
["farmId"] = {
["back"] = create3DLinkedText(back, 0, -0.041, -0.02, 0, 0, 0, 0.05, farmId),
["front"] = create3DLinkedText(front, 0, -0.041, -0.02, 0, 0, 0, 0.05, farmId)
},
["country"] = {
["back"] = create3DLinkedText(back, 0, 0.021, -0.015, 0, 0, 0, 0.03, countryCode),
["front"] = create3DLinkedText(front, 0, 0.021, -0.015, 0, 0, 0, 0.03, countryCode)
}
}
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_BASELINE)
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(1, 1, 1, 1)
setTextFont()
end
function VisualAnimal:setRightEarTag()
if self.nodes.earTagRight == nil then return end
for _, nodes in pairs(self.texts.earTagRight) do
for _, node in pairs(nodes) do delete3DLinkedText(node) end
end
local node = self.nodes.earTagRight
local colour = self.rightTextColour
local name = self.animal:getName()
local birthday = self.animal:getBirthday()
local day, month, year = birthday.day, birthday.month, birthday.year + RealisticLivestock.START_YEAR.PARTIAL
local birthdayText = string.format("%s%s/%s%s/%s%s", day < 10 and 0 or "", day, month < 10 and 0 or "", month, year < 10 and 0 or "", year)
local front = getChild(node, "front")
local back = getChild(node, "back")
set3DTextAutoScale(true)
set3DTextRemoveSpaces(true)
set3DTextWrapWidth(0.14)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_MIDDLE)
setTextAlignment(RenderText.ALIGN_CENTER)
setTextColor(colour[1], colour[2], colour[3], 1)
set3DTextWordsPerLine(1)
setTextLineHeightScale(0.75)
setTextFont(RealisticLivestock.FONTS.toms_handwritten)
self.texts.earTagRight = {
["name"] = {
["back"] = create3DLinkedText(back, 0, -0.01, -0.015, 0, 0, 0, 0.035, name),
["front"] = create3DLinkedText(front, 0, -0.01, -0.015, 0, 0, 0, 0.035, name)
}
}
set3DTextWrapWidth(0)
setTextFont(RealisticLivestock.FONTS.dejavu_sans)
self.texts.earTagRight.birthday = {
["back"] = create3DLinkedText(back, 0, 0.018, -0.015, 0, 0, 0, 0.02, birthdayText),
["front"] = create3DLinkedText(front, 0, 0.018, -0.015, 0, 0, 0, 0.02, birthdayText)
}
setTextLineHeightScale(1.1)
set3DTextWordsPerLine(0)
set3DTextAutoScale(false)
set3DTextRemoveSpaces(false)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_BASELINE)
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(1, 1, 1, 1)
setTextFont()
end
================================================
FILE: src/animals/husbandry/placeables/PlaceableHusbandry.lua
================================================
RL_PlaceableHusbandry = {}
function RL_PlaceableHusbandry.registerFunctions(placeable)
SpecializationUtil.registerFunction(placeable, "updateInputAndOutput", PlaceableHusbandry.updateInputAndOutput)
end
PlaceableHusbandry.registerFunctions = Utils.appendedFunction(PlaceableHusbandry.registerFunctions, RL_PlaceableHusbandry.registerFunctions)
function RL_PlaceableHusbandry:onHourChanged()
local animals = self.spec_husbandryAnimals:getClusters()
local temp = g_currentMission.environment.weather.temperatureUpdater.currentMin or 20
for _, animal in pairs(animals) do
animal:updateInput()
animal:updateOutput(temp)
end
if self.isServer then self:updateInputAndOutput(animals) end
end
PlaceableHusbandry.onHourChanged = Utils.appendedFunction(PlaceableHusbandry.onHourChanged, RL_PlaceableHusbandry.onHourChanged)
function PlaceableHusbandry:updateInputAndOutput(animals) end
================================================
FILE: src/animals/husbandry/placeables/PlaceableHusbandryLiquidManure.lua
================================================
RL_PlaceableHusbandryLiquidManure = {}
function RL_PlaceableHusbandryLiquidManure.registerOverwrittenFunctions(placeable)
SpecializationUtil.registerOverwrittenFunction(placeable, "updateInputAndOutput", PlaceableHusbandryLiquidManure.updateInputAndOutput)
end
PlaceableHusbandryLiquidManure.registerOverwrittenFunctions = Utils.appendedFunction(PlaceableHusbandryLiquidManure.registerOverwrittenFunctions, RL_PlaceableHusbandryLiquidManure.registerOverwrittenFunctions)
function RL_PlaceableHusbandryLiquidManure:onHusbandryAnimalsUpdate(_, _) end
PlaceableHusbandryLiquidManure.onHusbandryAnimalsUpdate = Utils.overwrittenFunction(PlaceableHusbandryLiquidManure.onHusbandryAnimalsUpdate, RL_PlaceableHusbandryLiquidManure.onHusbandryAnimalsUpdate)
function PlaceableHusbandryLiquidManure:updateInputAndOutput(superFunc, animals)
superFunc(self, animals)
local spec = self.spec_husbandryLiquidManure
spec.litersPerHour = 0
for _, animal in pairs(animals) do
local subType = animal:getSubType()
if subType ~= nil then
local liquidManure = subType.output.liquidManure
if liquidManure ~= nil then
spec.litersPerHour = spec.litersPerHour + animal:getOutput("liquidManure")
end
end
end
end
================================================
FILE: src/animals/husbandry/placeables/PlaceableHusbandryStraw.lua
================================================
RL_PlaceableHusbandryStraw = {}
function RL_PlaceableHusbandryStraw.registerOverwrittenFunctions(placeable)
SpecializationUtil.registerOverwrittenFunction(placeable, "updateInputAndOutput", PlaceableHusbandryStraw.updateInputAndOutput)
end
PlaceableHusbandryStraw.registerOverwrittenFunctions = Utils.appendedFunction(PlaceableHusbandryStraw.registerOverwrittenFunctions, RL_PlaceableHusbandryStraw.registerOverwrittenFunctions)
function RL_PlaceableHusbandryStraw:onHusbandryAnimalsUpdate(_, _) end
PlaceableHusbandryStraw.onHusbandryAnimalsUpdate = Utils.overwrittenFunction(PlaceableHusbandryStraw.onHusbandryAnimalsUpdate, RL_PlaceableHusbandryStraw.onHusbandryAnimalsUpdate)
function PlaceableHusbandryStraw:updateInputAndOutput(superFunc, animals)
superFunc(self, animals)
local spec = self.spec_husbandryStraw
spec.inputLitersPerHour = 0
spec.outputLitersPerHour = 0
for _, animal in pairs(animals) do
local subType = animal:getSubType()
if subType ~= nil then
local straw = subType.input.straw
if straw ~= nil then
spec.inputLitersPerHour = spec.inputLitersPerHour + animal:getInput("straw")
end
local manure = subType.output.manure
if manure ~= nil then
spec.outputLitersPerHour = spec.outputLitersPerHour + animal:getOutput("manure")
end
end
end
end
================================================
FILE: src/animals/husbandry/placeables/PlaceableHusbandryWater.lua
================================================
RL_PlaceableHusbandryWater = {}
function RL_PlaceableHusbandryWater.registerOverwrittenFunctions(placeable)
SpecializationUtil.registerOverwrittenFunction(placeable, "updateInputAndOutput", PlaceableHusbandryWater.updateInputAndOutput)
end
PlaceableHusbandryWater.registerOverwrittenFunctions = Utils.appendedFunction(PlaceableHusbandryWater.registerOverwrittenFunctions, RL_PlaceableHusbandryWater.registerOverwrittenFunctions)
function RL_PlaceableHusbandryWater:onHusbandryAnimalsUpdate(_, _) end
PlaceableHusbandryWater.onHusbandryAnimalsUpdate = Utils.overwrittenFunction(PlaceableHusbandryWater.onHusbandryAnimalsUpdate, RL_PlaceableHusbandryWater.onHusbandryAnimalsUpdate)
function PlaceableHusbandryWater:updateInputAndOutput(superFunc, animals)
superFunc(self, animals)
local spec = self.spec_husbandryWater
spec.litersPerHour = 0
for _, animal in pairs(animals) do
local subType = animal:getSubType()
if subType ~= nil then
local water = subType.input.water
if water ~= nil then
spec.litersPerHour = spec.litersPerHour + animal:getInput("water")
end
end
end
end
================================================
FILE: src/animals/husbandry/placeables/RealisticLivestock_PlaceableHusbandryAnimals.lua
================================================
RealisticLivestock_PlaceableHusbandryAnimals = {}
function RealisticLivestock_PlaceableHusbandryAnimals.registerFunctions(placeable)
SpecializationUtil.registerFunction(placeable, "setHasUnreadRLMessages", PlaceableHusbandryAnimals.setHasUnreadRLMessages)
SpecializationUtil.registerFunction(placeable, "getHasUnreadRLMessages", PlaceableHusbandryAnimals.getHasUnreadRLMessages)
SpecializationUtil.registerFunction(placeable, "getRLMessages", PlaceableHusbandryAnimals.getRLMessages)
SpecializationUtil.registerFunction(placeable, "addRLMessage", PlaceableHusbandryAnimals.addRLMessage)
SpecializationUtil.registerFunction(placeable, "deleteRLMessage", PlaceableHusbandryAnimals.deleteRLMessage)
SpecializationUtil.registerFunction(placeable, "getNextRLMessageUniqueId", PlaceableHusbandryAnimals.getNextRLMessageUniqueId)
SpecializationUtil.registerFunction(placeable, "setNextRLMessageUniqueId", PlaceableHusbandryAnimals.setNextRLMessageUniqueId)
SpecializationUtil.registerFunction(placeable, "getAIManager", PlaceableHusbandryAnimals.getAIManager)
end
PlaceableHusbandryAnimals.registerFunctions = Utils.appendedFunction(PlaceableHusbandryAnimals.registerFunctions, RealisticLivestock_PlaceableHusbandryAnimals.registerFunctions)
function PlaceableHusbandryAnimals:setHasUnreadRLMessages(hasUnreadMessages)
self.spec_husbandryAnimals.unreadMessages = hasUnreadMessages
end
function PlaceableHusbandryAnimals:getHasUnreadRLMessages()
return self.spec_husbandryAnimals.unreadMessages or false
end
function PlaceableHusbandryAnimals:getRLMessages()
return self.spec_husbandryAnimals.messages or {}
end
function PlaceableHusbandryAnimals:addRLMessage(id, animal, args, date, uniqueId, isLoading)
local spec = self.spec_husbandryAnimals
if spec.messages == nil then spec.messages = {} end
if date == nil then
local environment = g_currentMission.environment
local month = environment.currentPeriod + 2
local currentDayInPeriod = environment.currentDayInPeriod
if month > 12 then month = month - 12 end
local daysPerPeriod = environment.daysPerPeriod
local day = 1 + math.floor((currentDayInPeriod - 1) * (RealisticLivestock.DAYS_PER_MONTH[month] / daysPerPeriod))
local year = environment.currentYear
date = string.format("%s/%s/%s", day, month, year + RealisticLivestock.START_YEAR.FULL)
end
for i, arg in pairs(args or {}) do args[i] = tostring(arg) end
table.insert(spec.messages, {
["id"] = id,
["animal"] = animal,
["args"] = args or {},
["date"] = date,
["uniqueId"] = uniqueId or spec:getNextRLMessageUniqueId()
})
if not isLoading and #spec.messages > PlaceableHusbandryAnimals.maxNumMessages then table.remove(spec.messages, 1) end
spec.unreadMessages = true
end
function PlaceableHusbandryAnimals:deleteRLMessage(uniqueId)
local spec = self.spec_husbandryAnimals
for i, message in pairs(spec.messages or {}) do
if message.uniqueId == uniqueId then
table.remove(spec.messages, i)
return
end
end
end
function PlaceableHusbandryAnimals:setNextRLMessageUniqueId(nextUniqueId)
self.spec_husbandryAnimals.rlMessageUniqueId = nextUniqueId or 0
end
function PlaceableHusbandryAnimals:getNextRLMessageUniqueId()
local spec = self.spec_husbandryAnimals
if spec.rlMessageUniqueId == nil then spec.rlMessageUniqueId = 0 end
spec.rlMessageUniqueId = spec.rlMessageUniqueId + 1
return spec.rlMessageUniqueId
end
function RealisticLivestock_PlaceableHusbandryAnimals:saveToXMLFile(xmlFile, key)
local spec = self.spec_husbandryAnimals
xmlFile:setInt(key .. ".messages#uniqueId", spec.rlMessageUniqueId or 0)
xmlFile:setBool(key .. ".messages#unreadMessages", spec.unreadMessages or false)
for i, message in pairs(spec.messages or {}) do
local messageKey = string.format("%s.messages.message(%d)", key, i - 1)
xmlFile:setString(messageKey .. "#id", message.id)
xmlFile:setString(messageKey .. "#date", message.date)
if message.animal ~= nil then xmlFile:setString(messageKey .. "#animal", message.animal) end
xmlFile:setInt(messageKey .. "#uniqueId", message.uniqueId)
for j, arg in pairs(message.args) do
xmlFile:setString(string.format("%s.args.arg(%d)#value", messageKey, j - 1), arg)
end
end
spec.aiAnimalManager:saveToXMLFile(xmlFile, key)
end
PlaceableHusbandryAnimals.saveToXMLFile = Utils.prependedFunction(PlaceableHusbandryAnimals.saveToXMLFile, RealisticLivestock_PlaceableHusbandryAnimals.saveToXMLFile)
function RealisticLivestock_PlaceableHusbandryAnimals:loadFromXMLFile(xmlFile, key)
local spec = self.spec_husbandryAnimals
spec.rlMessageUniqueId = xmlFile:getInt(key .. ".messages#uniqueId", 0)
xmlFile:iterate(key .. ".messages.message", function(_, messageKey)
local id = xmlFile:getString(messageKey .. "#id")
local date = xmlFile:getString(messageKey .. "#date")
local animal = xmlFile:getString(messageKey .. "#animal")
local uniqueId = xmlFile:getInt(messageKey .. "#uniqueId")
local args = {}
xmlFile:iterate(messageKey .. ".args.arg", function(_, argKey)
table.insert(args, xmlFile:getString(argKey .. "#value"))
end)
self:addRLMessage(id, animal, args, date, uniqueId, true)
end)
spec.unreadMessages = xmlFile:getBool(key .. ".messages#unreadMessages", false)
spec.aiAnimalManager:loadFromXMLFile(xmlFile, key)
end
PlaceableHusbandryAnimals.loadFromXMLFile = Utils.prependedFunction(PlaceableHusbandryAnimals.loadFromXMLFile, RealisticLivestock_PlaceableHusbandryAnimals.loadFromXMLFile)
function PlaceableHusbandryAnimals:getAIManager()
local spec = self.spec_husbandryAnimals
if spec.aiAnimalManager == nil then spec.aiAnimalManager = AIAnimalManager.new(self) end
return spec.aiAnimalManager
end
function RealisticLivestock_PlaceableHusbandryAnimals:onLoad()
self.spec_husbandryAnimals.aiAnimalManager = AIAnimalManager.new(self, self.isServer)
end
PlaceableHusbandryAnimals.onLoad = Utils.appendedFunction(PlaceableHusbandryAnimals.onLoad, RealisticLivestock_PlaceableHusbandryAnimals.onLoad)
function RealisticLivestock_PlaceableHusbandryAnimals.onSettingChanged(name, state)
PlaceableHusbandryAnimals[name] = state
end
function RealisticLivestock_PlaceableHusbandryAnimals:updateVisualAnimals(_)
local spec = self.spec_husbandryAnimals
local animals = spec.clusterSystem:getAnimals()
spec.clusterHusbandry:setClusters(animals)
spec.clusterHusbandry:updateVisuals()
self:raiseActive()
end
PlaceableHusbandryAnimals.updateVisualAnimals = Utils.overwrittenFunction(PlaceableHusbandryAnimals.updateVisualAnimals, RealisticLivestock_PlaceableHusbandryAnimals.updateVisualAnimals)
--function RealisticLivestock_PlaceableHusbandryAnimals:addAnimals(_, subTypeIndex, numAnimals, age)
function RealisticLivestock_PlaceableHusbandryAnimals:addAnimals(_, animals)
--local newAnimals = {}
--for i=1, numAnimals do
--local subType = g_currentMission.animalSystem:getSubTypeByIndex(subTypeIndex)
--local animal = Animal.new(age, 100, 0, subType.gender, subTypeIndex, 0, false, false, false, self.spec_husbandryAnimals.clusterSystem)
--table.insert(newAnimals, animal)
--end
--if #newAnimals >= 1 then self:addCluster(newAnimals) end
for _, animal in pairs(animals) do self:addCluster(animal) end
end
PlaceableHusbandryAnimals.addAnimals = Utils.overwrittenFunction(PlaceableHusbandryAnimals.addAnimals, RealisticLivestock_PlaceableHusbandryAnimals.addAnimals)
function RealisticLivestock_PlaceableHusbandryAnimals:onDayChanged()
local minTemp = math.floor(g_currentMission.environment.weather.temperatureUpdater.currentMin)
local environment = g_currentMission.environment
local month = environment.currentPeriod + 2
local currentDayInPeriod = environment.currentDayInPeriod
if month > 12 then month = month - 12 end
local daysPerPeriod = environment.daysPerPeriod
local day = 1 + math.floor((currentDayInPeriod - 1) * (RealisticLivestock.DAYS_PER_MONTH[month] / daysPerPeriod))
local year = environment.currentYear
local spec = self.spec_husbandryAnimals
local animals = spec.clusterSystem:getAnimals()
local totalChildren, deadParents, childrenToSell, childrenToSellMoney, lowHealthDeaths, oldAgeDeaths, randomDeaths, randomDeathsMoney = 0, 0, 0, 0, 0, 0, 0, 0
for _, animal in ipairs(animals) do
if animal.monthsSinceLastBirth == nil then
animal.monthsSinceLastBirth = 0
end
if animal.isParent == nil then
animal.isParent = false
end
local a, b, c, d, e, f, g, h = animal:onDayChanged(spec, self.isServer, day, month, year, currentDayInPeriod, daysPerPeriod)
totalChildren = totalChildren + a
deadParents = deadParents + b
childrenToSell = childrenToSell + c
childrenToSellMoney = childrenToSellMoney + d
lowHealthDeaths = lowHealthDeaths + e
oldAgeDeaths = oldAgeDeaths + f
randomDeaths = randomDeaths + g
randomDeathsMoney = randomDeathsMoney + h
end
if self.isServer then
if childrenToSell > 0 and childrenToSellMoney > 0 then
local farmIndex = spec:getOwnerFarmId()
local farm = g_farmManager:getFarmById(farmIndex)
--if self.isServer then
g_currentMission:addMoneyChange(childrenToSellMoney, farmIndex, MoneyType.SOLD_ANIMALS, true)
--else
--g_client:getServerConnection():sendEvent(MoneyChangeEvent.new(childrenToSellMoney, MoneyType.SOLD_ANIMALS, farmIndex))
--end
if farm ~= nil then
farm:changeBalance(childrenToSellMoney, MoneyType.SOLD_ANIMALS)
end
end
if randomDeaths > 0 then
local farmIndex = spec:getOwnerFarmId()
local farm = g_farmManager:getFarmById(farmIndex)
if randomDeathsMoney > 0 then
--if self.isServer then
g_currentMission:addMoneyChange(randomDeathsMoney, farmIndex, MoneyType.SOLD_ANIMALS, true)
--else
--g_client:getServerConnection():sendEvent(MoneyChangeEvent.new(randomDeathsMoney, MoneyType.SOLD_ANIMALS, farmIndex))
--end
if farm ~= nil then
farm:changeBalance(randomDeathsMoney, MoneyType.SOLD_ANIMALS)
end
end
end
spec.aiAnimalManager:onDayChanged()
end
spec.minTemp = minTemp
if randomDeaths > 0 or oldAgeDeaths > 0 or lowHealthDeaths > 0 or deadParents > 0 or totalChildren > 0 then spec.clusterHusbandry:updateVisuals() end
self:raiseActive()
if self:getHasUnreadRLMessages() and g_localPlayer ~= nil and g_localPlayer.farmId == self:getOwnerFarmId() then
g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, string.format(g_i18n:getText("rl_ui_unreadMessages"), self:getName()))
end
end
PlaceableHusbandryAnimals.onDayChanged = Utils.overwrittenFunction(PlaceableHusbandryAnimals.onDayChanged, RealisticLivestock_PlaceableHusbandryAnimals.onDayChanged)
function RealisticLivestock_PlaceableHusbandryAnimals:onPeriodChanged(_)
if self.isServer then
local animals = self.spec_husbandryAnimals.clusterSystem:getClusters()
local totalTreatmentCost = 0
for _, animal in pairs(animals) do
local treatmentCost = animal:onPeriodChanged()
totalTreatmentCost = totalTreatmentCost + treatmentCost
end
if totalTreatmentCost > 0 then g_currentMission:addMoneyChange(totalTreatmentCost, self.spec_husbandryAnimals:getOwnerFarmId(), MoneyType.MEDICINE, true) end
g_diseaseManager:calculateTransmission(animals)
end
end
PlaceableHusbandryAnimals.onPeriodChanged = Utils.overwrittenFunction(PlaceableHusbandryAnimals.onPeriodChanged, RealisticLivestock_PlaceableHusbandryAnimals.onPeriodChanged)
================================================
FILE: src/animals/husbandry/placeables/RealisticLivestock_PlaceableHusbandryFood.lua
================================================
RealisticLivestock_PlaceableHusbandryFood = {}
function RealisticLivestock_PlaceableHusbandryFood.registerOverwrittenFunctions(placeable)
SpecializationUtil.registerOverwrittenFunction(placeable, "updateInputAndOutput", PlaceableHusbandryFood.updateInputAndOutput)
end
PlaceableHusbandryFood.registerOverwrittenFunctions = Utils.appendedFunction(PlaceableHusbandryFood.registerOverwrittenFunctions, RealisticLivestock_PlaceableHusbandryFood.registerOverwrittenFunctions)
function RealisticLivestock_PlaceableHusbandryFood:onHusbandryAnimalsUpdate(superFunc, animals) end
PlaceableHusbandryFood.onHusbandryAnimalsUpdate = Utils.overwrittenFunction(PlaceableHusbandryFood.onHusbandryAnimalsUpdate, RealisticLivestock_PlaceableHusbandryFood.onHusbandryAnimalsUpdate)
function RealisticLivestock_PlaceableHusbandryFood.onSettingChanged(name, state)
RealisticLivestock_PlaceableHusbandryFood[name] = state
end
function PlaceableHusbandryFood:updateInputAndOutput(superFunc, animals)
superFunc(self, animals)
local spec = self.spec_husbandryFood
spec.litersPerHour = 0
for _, animal in pairs(animals) do
local subType = animal:getSubType()
if subType ~= nil then
local food = subType.input.food
if food ~= nil then
spec.litersPerHour = spec.litersPerHour + animal:getInput("food")
end
end
end
end
================================================
FILE: src/animals/husbandry/placeables/RealisticLivestock_PlaceableHusbandryMilk.lua
================================================
RealisticLivestock_PlaceableHusbandryMilk = {}
function RealisticLivestock_PlaceableHusbandryMilk.registerOverwrittenFunctions(placeable)
SpecializationUtil.registerOverwrittenFunction(placeable, "updateInputAndOutput", PlaceableHusbandryMilk.updateInputAndOutput)
end
PlaceableHusbandryMilk.registerOverwrittenFunctions = Utils.appendedFunction(PlaceableHusbandryMilk.registerOverwrittenFunctions, RealisticLivestock_PlaceableHusbandryMilk.registerOverwrittenFunctions)
function RealisticLivestock_PlaceableHusbandryMilk:onHusbandryAnimalsUpdate(_, _) end
PlaceableHusbandryMilk.onHusbandryAnimalsUpdate = Utils.overwrittenFunction(PlaceableHusbandryMilk.onHusbandryAnimalsUpdate, RealisticLivestock_PlaceableHusbandryMilk.onHusbandryAnimalsUpdate)
function PlaceableHusbandryMilk:updateInputAndOutput(superFunc, animals)
superFunc(self, animals)
local spec = self.spec_husbandryMilk
for fillType, _ in pairs(spec.litersPerHour) do
spec.litersPerHour[fillType] = 0
end
spec.activeFillTypes = {}
for _, animal in pairs(animals) do
local subType = animal:getSubType()
if subType ~= nil then
local milk = subType.output.milk
if milk ~= nil then
spec.litersPerHour[milk.fillType] = spec.litersPerHour[milk.fillType] + animal:getOutput("milk")
table.addElement(spec.activeFillTypes, milk.fillType)
end
end
end
end
================================================
FILE: src/animals/husbandry/placeables/RealisticLivestock_PlaceableHusbandryPallets.lua
================================================
RealisticLivestock_PlaceableHusbandryPallets = {}
function RealisticLivestock_PlaceableHusbandryPallets.registerOverwrittenFunctions(placeable)
SpecializationUtil.registerOverwrittenFunction(placeable, "updateInputAndOutput", PlaceableHusbandryPallets.updateInputAndOutput)
end
PlaceableHusbandryPallets.registerOverwrittenFunctions = Utils.appendedFunction(PlaceableHusbandryPallets.registerOverwrittenFunctions, RealisticLivestock_PlaceableHusbandryPallets.registerOverwrittenFunctions)
function RealisticLivestock_PlaceableHusbandryPallets:onHusbandryAnimalsUpdate(_, _) end
PlaceableHusbandryPallets.onHusbandryAnimalsUpdate = Utils.overwrittenFunction(PlaceableHusbandryPallets.onHusbandryAnimalsUpdate, RealisticLivestock_PlaceableHusbandryPallets.onHusbandryAnimalsUpdate)
function PlaceableHusbandryPallets:updateInputAndOutput(superFunc, animals)
superFunc(self, animals)
local spec = self.spec_husbandryPallets
for fillType, _ in pairs(spec.litersPerHour) do
spec.litersPerHour[fillType] = 0
end
spec.activeFillTypes = {}
for _, animal in pairs(animals) do
local subType = animal:getSubType()
if subType ~= nil then
local pallets = subType.output.pallets
if pallets ~= nil then
spec.litersPerHour[pallets.fillType] = spec.litersPerHour[pallets.fillType] + animal:getOutput("pallets")
table.addElement(spec.activeFillTypes, pallets.fillType)
end
end
end
end
================================================
FILE: src/animals/shop/AnimalItemNew.lua
================================================
AnimalItemNew = {}
local animalItemNew_mt = Class(AnimalItemNew)
function AnimalItemNew.new(animal)
local self = setmetatable({}, animalItemNew_mt)
local animalSystem = g_currentMission.animalSystem
self.animal = animal
self.visual = animalSystem:getVisualByAge(animal.subTypeIndex, animal.age)
local subType = animal:getSubType()
local countryIndex = animal.birthday.country
local breederQuality = animalSystem:getFarmQuality(countryIndex, animal.farmId)
local breederQualityString
if breederQuality >= 1.65 then
breederQualityString = g_i18n:getText("rl_ui_genetics_extremelyGood")
elseif breederQuality >= 1.4 then
breederQualityString = g_i18n:getText("rl_ui_genetics_veryGood")
elseif breederQuality >= 1.1 then
breederQualityString = g_i18n:getText("rl_ui_genetics_good")
elseif breederQuality >= 0.9 then
breederQualityString = g_i18n:getText("rl_ui_genetics_average")
elseif breederQuality >= 0.7 then
breederQualityString = g_i18n:getText("rl_ui_genetics_bad")
elseif breederQuality >= 0.35 then
breederQualityString = g_i18n:getText("rl_ui_genetics_veryBad")
else
breederQualityString = g_i18n:getText("rl_ui_genetics_extremelyBad")
end
self.title = g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)
self.infos = {
{
["title"] = g_i18n:getText("rl_ui_breederQuality"),
["value"] = breederQualityString
},
{
["title"] = g_i18n:getText("rl_ui_animalOrigin"),
["value"] = RealisticLivestock.AREA_CODES[countryIndex].country
},
{
["title"] = g_i18n:getText("ui_age"),
["value"] = g_i18n:formatNumMonth(animal.age)
},
{
["title"] = g_i18n:getText("rl_ui_weight"),
["value"] = string.format("%.2fkg", animal.weight)
}
}
if animal.isPregnant then table.insert(self.infos, { ["title"] = g_i18n:getText("rl_ui_pregnant"), ["value"] = g_i18n:getText("rl_ui_yes") }) end
local genetics = animal:getGenetics()
local totalGenetics = 0
local totalGeneticsValues = 0
for key, value in pairs(genetics) do
if value == nil then continue end
local qualityText
if value >= 1.65 then
qualityText = g_i18n:getText("rl_ui_genetics_extremelyHigh")
elseif value >= 1.4 then
qualityText = g_i18n:getText("rl_ui_genetics_veryHigh")
elseif value >= 1.1 then
qualityText = g_i18n:getText("rl_ui_genetics_high")
elseif value >= 0.9 then
qualityText = g_i18n:getText("rl_ui_genetics_average")
elseif value >= 0.7 then
qualityText = g_i18n:getText("rl_ui_genetics_low")
elseif value >= 0.35 then
qualityText = g_i18n:getText("rl_ui_genetics_veryLow")
else
qualityText = g_i18n:getText("rl_ui_genetics_extremelyLow")
end
local keyText = key
if key == "productivity" then
if animal.animalTypeIndex == AnimalType.COW then
keyText = "milk"
elseif animal.animalTypeIndex == AnimalType.SHEEP then
keyText = "wool"
elseif animal.animalTypeIndex == AnimalType.CHICKEN then
keyText = "eggs"
end
elseif key == "quality" then
keyText = "meat"
end
table.insert(self.infos, { ["title"] = g_i18n:getText("rl_ui_" .. keyText), ["value"] = qualityText, ["colour"] = { 1 - value / 1.75, value / 1.75, 0 } })
totalGenetics = totalGenetics + 1
totalGeneticsValues = totalGeneticsValues + value
end
local averageGenetics = totalGeneticsValues / totalGenetics
if averageGenetics >= 1.65 then
qualityText = g_i18n:getText("rl_ui_genetics_extremelyGood")
elseif averageGenetics >= 1.4 then
qualityText = g_i18n:getText("rl_ui_genetics_veryGood")
elseif averageGenetics >= 1.1 then
qualityText = g_i18n:getText("rl_ui_genetics_good")
elseif averageGenetics >= 0.9 then
qualityText = g_i18n:getText("rl_ui_genetics_average")
elseif averageGenetics >= 0.7 then
qualityText = g_i18n:getText("rl_ui_genetics_bad")
elseif averageGenetics >= 0.35 then
qualityText = g_i18n:getText("rl_ui_genetics_veryBad")
else
qualityText = g_i18n:getText("rl_ui_genetics_extremelyBad")
end
table.insert(self.infos, { ["title"] = g_i18n:getText("rl_ui_overall"), ["value"] = qualityText, ["colour"] = { 1 - averageGenetics / 1.75, averageGenetics / 1.75, 0 } })
return self
end
function AnimalItemNew:getName()
local animal = self.animal
return animal.name or string.format("%s %s %s", RealisticLivestock.AREA_CODES[animal.birthday.country].code, animal.farmId, animal.uniqueId)
end
function AnimalItemNew:getTitle()
return self.title
end
function AnimalItemNew:getPrice()
return self.animal:getSellPrice() * 1.075
end
function AnimalItemNew:getTranportationFee(_)
return g_currentMission.animalSystem:getAnimalTransportFee(self.animal.subTypeIndex, self.animal.age)
end
function AnimalItemNew:getSubTypeIndex()
return self.animal.subTypeIndex
end
function AnimalItemNew:getAge()
return self.animal.age
end
function AnimalItemNew:getDescription()
return self.visual.store.description
end
function AnimalItemNew:getFilename()
return self.visual.store.imageFilename
end
function AnimalItemNew:getInfos()
return self.infos
end
function AnimalItemNew:getHasAnyDisease()
return self.animal:getHasAnyDisease()
end
================================================
FILE: src/animals/shop/RealisticLivestock_AnimalItemStock.lua
================================================
RealisticLivestock_AnimalItemStock = {}
local mt = Class(AnimalItemStock)
function RealisticLivestock_AnimalItemStock:getClusterId(_)
return self.cluster.isIndividual == nil and self.cluster.id or (self.cluster.farmId .. " " .. self.cluster.uniqueId .. " " .. self.cluster.birthday.country)
end
AnimalItemStock.getClusterId = Utils.overwrittenFunction(AnimalItemStock.getClusterId, RealisticLivestock_AnimalItemStock.getClusterId)
function RealisticLivestock_AnimalItemStock.new(animal)
local self = setmetatable({}, mt)
self.cluster = animal
self.visual = g_currentMission.animalSystem:getVisualByAge(animal.subTypeIndex, animal:getAge())
local subType = g_currentMission.animalSystem:getSubTypeByIndex(animal.subTypeIndex)
self.title = g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)
local hasMonitor = animal.monitor.active or animal.monitor.removed
self.infos = {
{
title = g_i18n:getText("ui_age"),
value = g_i18n:formatNumMonth(animal:getAge())
}
}
if hasMonitor then
table.insert(self.infos, {
title = g_i18n:getText("ui_horseHealth"),
value = string.format("%.f%%", animal:getHealthFactor() * 100)
})
end
if subType.supportsReproduction and animal.reproduction > 0 and animal:getAge() >= subType.reproductionMinAgeMonth then
local newInfo = {
title = g_i18n:getText("infohud_reproductionStatus"),
value = string.format("%.f%%", animal:getReproductionFactor() * 100)
}
table.insert(self.infos, newInfo)
end
if animal.isIndividual then
local yes = g_i18n:getText("rl_ui_yes")
local no = g_i18n:getText("rl_ui_no")
if subType.supportsReproduction and animal.reproduction <= 0 then
local valueText = nil
local healthFactor = animal:getHealthFactor()
if animal.age < subType.reproductionMinAgeMonth then
valueText = g_i18n:getText("rl_ui_tooYoung")
elseif animal.isParent and animal.monthsSinceLastBirth <= 2 then
valueText = g_i18n:getText("rl_ui_recoveringLastBirth")
elseif not RealisticLivestock.hasMaleAnimalInPen(animal.clusterSystem, animal.subType, animal) then
valueText = g_i18n:getText("rl_ui_noMaleAnimal")
elseif healthFactor < subType.reproductionMinHealth then
valueText = g_i18n:getText("rl_ui_unhealthy")
end
if valueText ~= nil then
table.insert(self.infos, {
title = g_i18n:getText("rl_ui_canReproduce"),
value = valueText
})
end
end
local pregnancy = animal.pregnancy
if pregnancy ~= nil and pregnancy.pregnancies and #pregnancy.pregnancies > 0 then
table.insert(self.infos, { ["title"] = g_i18n:getText("rl_ui_pregnancyExpecting"), ["value"] = string.format("%s %s", #pregnancy.pregnancies, g_i18n:getText("rl_ui_pregnancy" .. (#pregnancy.pregnancies == 1 and "Baby" or "Babies"))) })
table.insert(self.infos, { ["title"] = g_i18n:getText("rl_ui_pregnancyExpected"), ["value"] = string.format("%s/%s/%s", pregnancy.expected.day, pregnancy.expected.month, pregnancy.expected.year + RealisticLivestock.START_YEAR.FULL) })
end
if hasMonitor then
table.insert(self.infos, {
title = g_i18n:getText("rl_ui_weight"),
value = string.format("%.2f", animal.weight or 50) .. "kg"
})
table.insert(self.infos, {
title = g_i18n:getText("rl_ui_targetWeight"),
value = string.format("%.2f", animal.targetWeight or 50) .. "kg"
})
if animal.animalTypeIndex == AnimalType.COW and animal.gender == "female" and animal:getAge() >= subType.reproductionMinAgeMonth then
table.insert(self.infos, {
title = g_i18n:getText("rl_ui_lactating"),
value = animal.isLactating and yes or no
})
end
end
if animal.gender == "male" and animal:getAge() >= subType.reproductionMinAgeMonth then
table.insert(self.infos, {
title = g_i18n:getText("rl_ui_maleNumImpregnatable"),
value = animal:getNumberOfImpregnatableFemalesForMale() or 0
})
end
end
if animal.animalTypeIndex == AnimalType.HORSE then
table.insert(self.infos, { ["title"] = g_i18n:getText("ui_horseFitness"), ["value"] = string.format("%.f%%", animal:getFitnessFactor() * 100) })
table.insert(self.infos, { ["title"] = g_i18n:getText("ui_horseDailyRiding"), ["value"] = string.format("%.f%%", animal:getRidingFactor() * 100) })
if Platform.gameplay.needHorseCleaning then table.insert(self.infos, { ["title"] = g_i18n:getText("statistic_cleanliness"), ["value"] = string.format("%.f%%", (1 - animal:getDirtFactor()) * 100) }) end
end
return self
end
AnimalItemStock.new = RealisticLivestock_AnimalItemStock.new
function AnimalItemStock:getHasAnyDisease()
return self.cluster:getHasAnyDisease()
end
================================================
FILE: src/animals/shop/controllers/AnimalScreenBase.lua
================================================
RL_AnimalScreenBase = {}
function RL_AnimalScreenBase:getTargetItems(_)
return self.targetItems
end
AnimalScreenBase.getTargetItems = Utils.overwrittenFunction(AnimalScreenBase.getTargetItems, RL_AnimalScreenBase.getTargetItems)
function RL_AnimalScreenBase.sortAnimals(a, b)
if a.cluster == nil or b.cluster == nil then return true end
local aDisease, bDisease = a.cluster:getHasAnyDisease(), b.cluster:getHasAnyDisease()
if aDisease or bDisease then
if aDisease and not bDisease then return true end
if bDisease and not aDisease then return false end
end
if a.cluster.subTypeIndex == b.cluster.subTypeIndex then return a.cluster.age < b.cluster.age end
return a.cluster.subTypeIndex < b.cluster.subTypeIndex
end
function RL_AnimalScreenBase.sortSaleAnimals(a, b)
if a.animal == nil or b.animal == nil then return true end
local aDisease, bDisease = a.animal:getHasAnyDisease(), b.animal:getHasAnyDisease()
if aDisease or bDisease then
if aDisease and not bDisease then return true end
if bDisease and not aDisease then return false end
end
local aValue = a.animal:getSellPrice()
local bValue = b.animal:getSellPrice()
if a.animal.subTypeIndex == b.animal.subTypeIndex then
if aValue == bValue then return a.animal.age < b.animal.age end
return aValue > bValue
end
return a.animal.subTypeIndex < b.animal.subTypeIndex
end
function RL_AnimalScreenBase:onAnimalsChanged(_)
if self.trailer == nil then return end
self:initItems()
self.animalsChangedCallback()
self.trailer:updateAnimals()
end
AnimalScreenTrailerFarm.onAnimalMovedToTrailer = Utils.appendedFunction(AnimalScreenTrailerFarm.onAnimalMovedToTrailer, RL_AnimalScreenBase.onAnimalsChanged)
AnimalScreenTrailerFarm.onAnimalMovedToFarm = Utils.appendedFunction(AnimalScreenTrailerFarm.onAnimalMovedToFarm, RL_AnimalScreenBase.onAnimalsChanged)
AnimalScreenTrailerFarm.onAnimalsChanged = Utils.appendedFunction(AnimalScreenTrailerFarm.onAnimalsChanged, RL_AnimalScreenBase.onAnimalsChanged)
AnimalScreenDealerTrailer.onAnimalBought = Utils.appendedFunction(AnimalScreenDealerTrailer.onAnimalBought, RL_AnimalScreenBase.onAnimalsChanged)
AnimalScreenDealerTrailer.onAnimalSold = Utils.appendedFunction(AnimalScreenDealerTrailer.onAnimalSold, RL_AnimalScreenBase.onAnimalsChanged)
AnimalScreenDealerTrailer.onAnimalsChanged = Utils.appendedFunction(AnimalScreenDealerTrailer.onAnimalsChanged, RL_AnimalScreenBase.onAnimalsChanged)
AnimalScreenTrailer.onAnimalLoadedToTrailer = Utils.appendedFunction(AnimalScreenTrailer.onAnimalLoadedToTrailer, RL_AnimalScreenBase.onAnimalsChanged)
AnimalScreenTrailer.onAnimalsChanged = Utils.appendedFunction(AnimalScreenTrailer.onAnimalsChanged, RL_AnimalScreenBase.onAnimalsChanged)
function AnimalScreenBase:setSourceBulkActionFinishedCallback(callback, target)
function self.sourceBulkActionFinished(error, text, indexes)
callback(target, error, text, indexes)
end
end
function AnimalScreenBase:setTargetBulkActionFinishedCallback(callback, target)
function self.targetBulkActionFinished(error, text, indexes)
callback(target, error, text, indexes)
end
end
================================================
FILE: src/animals/shop/controllers/AnimalScreenDealer.lua
================================================
RL_AnimalScreenDealer = {}
function RL_AnimalScreenDealer:initItems()
AnimalScreenDealer:superClass().initItems(self)
self.husbandries = {}
self.targetHusbandries = {}
self.targetAnimalTypes = {}
local placeables = g_currentMission.husbandrySystem:getPlaceablesByFarm()
local animalSystem = g_currentMission.animalSystem
for _, placeable in pairs(placeables) do
local animalTypeIndex = placeable:getAnimalTypeIndex()
if self.husbandries[animalTypeIndex] == nil then self.husbandries[animalTypeIndex] = {} end
if placeable:getNumOfAnimals() > 0 then
table.insert(self.targetAnimalTypes, animalSystem:getTypeByIndex(animalTypeIndex))
table.insert(self.targetHusbandries, placeable)
end
table.insert(self.husbandries[animalTypeIndex], placeable)
end
table.sort(self.targetAnimalTypes, function(a, b) return a.typeIndex < b.typeIndex end)
table.sort(self.targetHusbandries, function(a, b) return a:getAnimalTypeIndex() < b:getAnimalTypeIndex() end)
end
AnimalScreenDealer.initItems = Utils.overwrittenFunction(AnimalScreenDealer.initItems, RL_AnimalScreenDealer.initItems)
function RL_AnimalScreenDealer:setCurrentHusbandry(_, animalTypeIndex, index, isBuyMode)
if isBuyMode then
local husbandries = self.husbandries[animalTypeIndex]
local husbandry
if husbandries == nil then
husbandry = nil
else
husbandry = husbandries[index] or nil
end
self.husbandry = husbandry
else
self.husbandry = self.targetHusbandries[index]
end
self:initTargetItems()
end
AnimalScreenDealer.setCurrentHusbandry = Utils.overwrittenFunction(AnimalScreenDealer.setCurrentHusbandry, RL_AnimalScreenDealer.setCurrentHusbandry)
function RL_AnimalScreenDealer:initTargetItems(_)
self.targetItems = {}
if self.husbandry == nil then return end
local animals = self.husbandry:getClusters()
if animals ~= nil then
for _, animal in pairs(animals) do
local item = AnimalItemStock.new(animal)
table.insert(self.targetItems, item)
end
end
table.sort(self.targetItems, RL_AnimalScreenBase.sortAnimals)
end
AnimalScreenDealer.initTargetItems = Utils.overwrittenFunction(AnimalScreenDealer.initTargetItems, RL_AnimalScreenDealer.initTargetItems)
function RL_AnimalScreenDealer:initSourceItems(_)
self.sourceItems = {}
local animalSystem = g_currentMission.animalSystem
self.sourceAnimalTypes = animalSystem:getTypes()
local animalTypes = {}
if g_localPlayer == nil then return end
local farm = g_localPlayer.farmId
for _, placeable in pairs(g_currentMission.placeableSystem.placeables) do
if placeable.ownerFarmId == farm and placeable.spec_husbandryAnimals then
local animalType = placeable.spec_husbandryAnimals:getAnimalTypeIndex()
animalTypes[animalType] = true
end
end
--for i = #self.sourceAnimalTypes, 1, -1 do
--local animalType = self.sourceAnimalTypes[i]
--if not animalTypes[animalType.typeIndex] then table.remove(self.sourceAnimalTypes, i) end
--end
for index, animalType in pairs(self.sourceAnimalTypes) do
local animals = animalSystem:getSaleAnimalsByTypeIndex(animalType.typeIndex)
self.sourceItems[animalType.typeIndex] = {}
for _, animal in pairs(animals) do
local item = AnimalItemNew.new(animal)
table.insert(self.sourceItems[animalType.typeIndex], item)
end
table.sort(self.sourceItems[animalType.typeIndex], RL_AnimalScreenBase.sortSaleAnimals)
end
end
AnimalScreenDealer.initSourceItems = Utils.overwrittenFunction(AnimalScreenDealer.initSourceItems, RL_AnimalScreenDealer.initSourceItems)
function RL_AnimalScreenDealer:getSourceMaxNumAnimals(_, _)
return 1
end
AnimalScreenDealer.getSourceMaxNumAnimals = Utils.overwrittenFunction(AnimalScreenDealer.getSourceMaxNumAnimals, RL_AnimalScreenDealer.getSourceMaxNumAnimals)
function RL_AnimalScreenDealer:applySource(_, animalTypeIndex, animalIndex)
if self.husbandry == nil then return false end
self.sourceAnimals = nil
local item = self.sourceItems[animalTypeIndex][animalIndex]
local husbandry = self.husbandry
local ownerFarmId = husbandry:getOwnerFarmId()
local price = -item:getPrice()
local transportationFee = -item:getTranportationFee(1)
local errorCode = AnimalBuyEvent.validate(husbandry, item:getSubTypeIndex(), item:getAge(), 1, price, transportationFee, ownerFarmId)
if errorCode ~= nil then
local error = AnimalScreenDealerFarm.BUY_ERROR_CODE_MAPPING[errorCode]
self.errorCallback(g_i18n:getText(error.text))
return false
end
--self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
local animal = item.animal or item.cluster
self.sourceAnimals = { animal }
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
g_messageCenter:subscribe(AnimalBuyEvent, self.onAnimalBought, self)
g_client:getServerConnection():sendEvent(AnimalBuyEvent.new(husbandry, self.sourceAnimals, price, transportationFee))
--husbandry:getClusterSystem():addCluster(animal)
--g_currentMission:addMoney(price + transportationFee, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
--g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
--table.remove(self.sourceItems[animalTypeIndex], animalIndex)
--self.sourceActionFinished(nil, "Animal bought successfully")
return true
end
AnimalScreenDealer.applySource = Utils.overwrittenFunction(AnimalScreenDealer.applySource, RL_AnimalScreenDealer.applySource)
function RL_AnimalScreenDealer:onAnimalBought(errorCode)
if errorCode == AnimalBuyEvent.BUY_SUCCESS and self.sourceAnimals ~= nil then
for _, animal in pairs(self.sourceAnimals) do g_currentMission.animalSystem:removeSaleAnimal(animal.animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId) end
end
end
AnimalScreenDealer.onAnimalBought = Utils.prependedFunction(AnimalScreenDealer.onAnimalBought, RL_AnimalScreenDealer.onAnimalBought)
function RL_AnimalScreenDealer:applyTarget(_, animalTypeIndex, animalIndex)
if self.husbandry == nil then return false end
local item = self.targetItems[animalIndex]
local husbandry = self.husbandry
local ownerFarmId = husbandry:getOwnerFarmId()
local price = item:getPrice()
local transportationFee = -item:getTranportationFee(1)
local errorCode = AnimalSellEvent.validate(husbandry, item:getClusterId(), 1, price, transportationFee)
if errorCode ~= nil then
local error = AnimalScreenDealerFarm.SELL_ERROR_CODE_MAPPING[errorCode]
self.errorCallback(g_i18n:getText(error.text))
return false
end
--self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
local animal = item.animal or item.cluster
husbandry:getClusterSystem():removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
g_currentMission:addMoney(price + transportationFee, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
table.remove(self.targetItems, animalIndex)
self.targetActionFinished(nil, "Animal sold successfully")
return true
end
AnimalScreenDealer.applyTarget = Utils.overwrittenFunction(AnimalScreenDealer.applyTarget, RL_AnimalScreenDealer.applyTarget)
function RL_AnimalScreenDealer:getSourcePrice(_, animalTypeIndex, animalIndex, _)
if self.sourceItems[animalTypeIndex] ~= nil then
local item = self.sourceItems[animalTypeIndex][animalIndex]
if item ~= nil then
local price = item:getPrice()
local transportationFee = item:getTranportationFee(1)
return true, price, transportationFee, price + transportationFee
end
end
return false, 0, 0, 0
end
AnimalScreenDealer.getSourcePrice = Utils.overwrittenFunction(AnimalScreenDealer.getSourcePrice, RL_AnimalScreenDealer.getSourcePrice)
function AnimalScreenDealer:applySourceBulk(animalTypeIndex, items)
if self.husbandry == nil then return false end
self.sourceAnimals = {}
local husbandry = self.husbandry
local clusterSystem = husbandry:getClusterSystem()
local ownerFarmId = husbandry:getOwnerFarmId()
local sourceItems = self.sourceItems[animalTypeIndex]
--local indexesToRemove = {}
--local indexesToReturn = {}
local totalPrice = 0
local totalTransportPrice = 0
local totalBoughtAnimals = 0
for _, item in pairs(items) do
if sourceItems[item] ~= nil then
local sourceItem = sourceItems[item]
local animal = sourceItem.animal
local price = -sourceItem:getPrice()
local transportationFee = -sourceItem:getTranportationFee(1)
local errorCode = AnimalBuyEvent.validate(husbandry, animal.subTypeIndex, animal.age, 1, price, transportationFee, ownerFarmId)
if errorCode ~= nil then continue end
totalBoughtAnimals = totalBoughtAnimals + 1
totalPrice = totalPrice + price
totalTransportPrice = totalTransportPrice + transportationFee
table.insert(self.sourceAnimals, animal)
--clusterSystem:addCluster(animal)
--g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(sourceItems, indexesToRemove[i]) end
--self.sourceItems[animalTypeIndex] = sourceItems
--g_currentMission:addMoney(totalPrice, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
--self.sourceBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_buyBulkResult"), totalBoughtAnimals, g_i18n:formatMoney(math.abs(totalPrice), 2, true, true)), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
g_messageCenter:subscribe(AnimalBuyEvent, self.onAnimalBought, self)
g_client:getServerConnection():sendEvent(AnimalBuyEvent.new(husbandry, self.sourceAnimals, totalPrice, totalTransportPrice))
end
function AnimalScreenDealer:applyTargetBulk(animalTypeIndex, items)
if self.husbandry == nil then return false end
self.targetAnimals = {}
local husbandry = self.husbandry
local clusterSystem = husbandry:getClusterSystem()
local ownerFarmId = husbandry:getOwnerFarmId()
local targetItems = self.targetItems
--local indexesToRemove = {}
--local indexesToReturn = {}
local totalPrice = 0
local totalTransportPrice = 0
local totalSoldAnimals = 0
for _, item in pairs(items) do
if targetItems[item] ~= nil then
local targetItem = targetItems[item]
local animal = targetItem.animal or targetItem.cluster
local price = targetItem:getPrice()
local transportationFee = -targetItem:getTranportationFee(1)
local errorCode = AnimalSellEvent.validate(husbandry, targetItem:getClusterId(), 1, price, transportationFee)
if errorCode ~= nil then continue end
totalSoldAnimals = totalSoldAnimals + 1
totalPrice = totalPrice + price
totalTransportPrice = totalTransportPrice + transportationFee
table.insert(self.targetAnimals, animal)
--clusterSystem:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(targetItems, indexesToRemove[i]) end
--self.targetItems = targetItems
--g_currentMission:addMoney(totalPrice, ownerFarmId, MoneyType.SOLD_ANIMALS, true, true)
--self.targetBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_sellBulkResult"), totalSoldAnimals, g_i18n:formatMoney(math.abs(totalPrice), 2, true, true)), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.SELLING))
g_messageCenter:subscribe(AnimalSellEvent, self.onAnimalSold, self)
g_client:getServerConnection():sendEvent(AnimalSellEvent.new(husbandry, self.targetAnimals, totalPrice, totalTransportPrice))
end
================================================
FILE: src/animals/shop/controllers/AnimalScreenDealerFarm.lua
================================================
RL_AnimalScreenDealerFarm = {}
function RL_AnimalScreenDealerFarm:initTargetItems(_)
self.targetItems = {}
local animals = self.husbandry:getClusters()
if animals ~= nil then
for _, animal in pairs(animals) do
local item = AnimalItemStock.new(animal)
table.insert(self.targetItems, item)
end
end
table.sort(self.targetItems, RL_AnimalScreenBase.sortAnimals)
end
AnimalScreenDealerFarm.initTargetItems = Utils.overwrittenFunction(AnimalScreenDealerFarm.initTargetItems, RL_AnimalScreenDealerFarm.initTargetItems)
function RL_AnimalScreenDealerFarm:initSourceItems(_)
local animalTypeIndex = self.husbandry:getAnimalTypeIndex()
local animals = g_currentMission.animalSystem:getSaleAnimalsByTypeIndex(animalTypeIndex)
self.sourceItems = { [animalTypeIndex] = {} }
for _, animal in pairs(animals) do
local item = AnimalItemNew.new(animal)
table.insert(self.sourceItems[animalTypeIndex], item)
end
table.sort(self.sourceItems[animalTypeIndex], RL_AnimalScreenBase.sortSaleAnimals)
end
AnimalScreenDealerFarm.initSourceItems = Utils.overwrittenFunction(AnimalScreenDealerFarm.initSourceItems, RL_AnimalScreenDealerFarm.initSourceItems)
function RL_AnimalScreenDealerFarm:getSourceMaxNumAnimals(_, _)
return 1
end
AnimalScreenDealerFarm.getSourceMaxNumAnimals = Utils.overwrittenFunction(AnimalScreenDealerFarm.getSourceMaxNumAnimals, RL_AnimalScreenDealerFarm.getSourceMaxNumAnimals)
function RL_AnimalScreenDealerFarm:applySource(_, animalTypeIndex, animalIndex)
self.sourceAnimals = nil
local item = self.sourceItems[animalTypeIndex][animalIndex]
local husbandry = self.husbandry
local ownerFarmId = husbandry:getOwnerFarmId()
local price = -item:getPrice()
local transportationFee = -item:getTranportationFee(1)
local errorCode = AnimalBuyEvent.validate(husbandry, item:getSubTypeIndex(), item:getAge(), 1, price, transportationFee, ownerFarmId)
if errorCode ~= nil then
local error = AnimalScreenDealerFarm.BUY_ERROR_CODE_MAPPING[errorCode]
self.errorCallback(g_i18n:getText(error.text))
return false
end
local animal = item.animal
self.sourceAnimals = { animal }
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
g_messageCenter:subscribe(AnimalBuyEvent, self.onAnimalBought, self)
g_client:getServerConnection():sendEvent(AnimalBuyEvent.new(husbandry, self.sourceAnimals, price, transportationFee))
--husbandry:getClusterSystem():addCluster(animal)
-- g_currentMission:addMoney(price + transportationFee, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
--g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
--table.remove(self.sourceItems[animalTypeIndex], animalIndex)
--self.sourceActionFinished(nil, "Animal bought successfully")
self.husbandry:addRLMessage("BOUGHT_ANIMALS_SINGLE", nil, { g_i18n:formatMoney(math.abs(price + transportationFee), 2, true, true) })
return true
end
AnimalScreenDealerFarm.applySource = Utils.overwrittenFunction(AnimalScreenDealerFarm.applySource, RL_AnimalScreenDealerFarm.applySource)
function RL_AnimalScreenDealerFarm:onAnimalBought(errorCode)
if errorCode == AnimalBuyEvent.BUY_SUCCESS and self.sourceAnimals ~= nil then
for _, animal in pairs(self.sourceAnimals) do g_currentMission.animalSystem:removeSaleAnimal(animal.animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId) end
end
end
AnimalScreenDealerFarm.onAnimalBought = Utils.prependedFunction(AnimalScreenDealerFarm.onAnimalBought, RL_AnimalScreenDealerFarm.onAnimalBought)
function RL_AnimalScreenDealerFarm:applyTarget(_, animalTypeIndex, animalIndex)
self.targetAnimals = nil
local item = self.targetItems[animalIndex]
local husbandry = self.husbandry
local ownerFarmId = husbandry:getOwnerFarmId()
local price = item:getPrice()
local transportationFee = -item:getTranportationFee(1)
--local errorCode = AnimalSellEvent.validate(husbandry, item:getClusterId(), 1, price, transportationFee)
--if errorCode ~= nil then
--local error = AnimalScreenDealerFarm.SELL_ERROR_CODE_MAPPING[errorCode]
--self.errorCallback(g_i18n:getText(error.text))
--return false
--end
--self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
local animal = item.animal or item.cluster
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_TARGET, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.SELLING))
self.targetAnimals = { animal }
g_messageCenter:subscribe(AnimalSellEvent, self.onAnimalSold, self)
g_client:getServerConnection():sendEvent(AnimalSellEvent.new(husbandry, self.targetAnimals, price, transportationFee))
--husbandry:getClusterSystem():removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--g_currentMission:addMoney(price + transportationFee, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
--g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
--table.remove(self.targetItems, animalIndex)
--self.targetActionFinished(nil, "Animal sold successfully")
self.husbandry:addRLMessage("SOLD_ANIMALS_SINGLE", nil, { g_i18n:formatMoney(price + transportationFee, 2, true, true) })
return true
end
AnimalScreenDealerFarm.applyTarget = Utils.overwrittenFunction(AnimalScreenDealerFarm.applyTarget, RL_AnimalScreenDealerFarm.applyTarget)
function RL_AnimalScreenDealerFarm:getSourcePrice(_, animalTypeIndex, animalIndex, _)
if self.sourceItems[animalTypeIndex] ~= nil then
local item = self.sourceItems[animalTypeIndex][animalIndex]
if item ~= nil then
local price = item:getPrice()
local transportationFee = item:getTranportationFee(1)
return true, price, transportationFee, price + transportationFee
end
end
return false, 0, 0, 0
end
AnimalScreenDealerFarm.getSourcePrice = Utils.overwrittenFunction(AnimalScreenDealerFarm.getSourcePrice, RL_AnimalScreenDealerFarm.getSourcePrice)
function AnimalScreenDealerFarm:applySourceBulk(animalTypeIndex, items)
self.sourceAnimals = {}
local husbandry = self.husbandry
local clusterSystem = husbandry:getClusterSystem()
local ownerFarmId = husbandry:getOwnerFarmId()
local sourceItems = self.sourceItems[animalTypeIndex]
--local indexesToRemove = {}
--local indexesToReturn = {}
local totalPrice = 0
local totalTransportPrice = 0
local totalBoughtAnimals = 0
for _, item in pairs(items) do
if sourceItems[item] ~= nil then
local sourceItem = sourceItems[item]
local animal = sourceItem.animal
local price = -sourceItem:getPrice()
local transportationFee = -sourceItem:getTranportationFee(1)
local errorCode = AnimalBuyEvent.validate(husbandry, animal.subTypeIndex, animal.age, 1, price, transportationFee, ownerFarmId)
if errorCode ~= nil then continue end
totalBoughtAnimals = totalBoughtAnimals + 1
totalPrice = totalPrice + price
totalTransportPrice = totalTransportPrice + transportationFee
table.insert(self.sourceAnimals, animal)
--clusterSystem:addCluster(animal)
--g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(sourceItems, indexesToRemove[i]) end
-- self.sourceItems[animalTypeIndex] = sourceItems
--g_currentMission:addMoney(totalPrice, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
--self.sourceBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_buyBulkResult"), totalBoughtAnimals, g_i18n:formatMoney(math.abs(totalPrice), 2, true, true)), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
g_messageCenter:subscribe(AnimalBuyEvent, self.onAnimalBought, self)
g_client:getServerConnection():sendEvent(AnimalBuyEvent.new(husbandry, self.sourceAnimals, totalPrice, totalTransportPrice))
if totalBoughtAnimals == 1 then
self.husbandry:addRLMessage("BOUGHT_ANIMALS_SINGLE", nil, { g_i18n:formatMoney(math.abs(totalPrice + totalTransportPrice), 2, true, true) })
elseif totalBoughtAnimals > 0 then
self.husbandry:addRLMessage("BOUGHT_ANIMALS_MULTIPLE", nil, { totalBoughtAnimals, g_i18n:formatMoney(math.abs(totalPrice + totalTransportPrice), 2, true, true) })
end
end
function AnimalScreenDealerFarm:applyTargetBulk(animalTypeIndex, items)
self.targetAnimals = {}
local husbandry = self.husbandry
local clusterSystem = husbandry:getClusterSystem()
local ownerFarmId = husbandry:getOwnerFarmId()
local targetItems = self.targetItems
local indexesToRemove = {}
local indexesToReturn = {}
local totalPrice = 0
local totalTransportPrice = 0
local totalSoldAnimals = 0
for _, item in pairs(items) do
if targetItems[item] ~= nil then
local targetItem = targetItems[item]
local animal = targetItem.animal or targetItem.cluster
local price = targetItem:getPrice()
local transportationFee = -targetItem:getTranportationFee(1)
local errorCode = AnimalSellEvent.validate(husbandry, targetItem:getClusterId(), 1, price, transportationFee)
if errorCode ~= nil then continue end
totalSoldAnimals = totalSoldAnimals + 1
totalPrice = totalPrice + price
totalTransportPrice = totalTransportPrice + transportationFee
table.insert(self.targetAnimals, animal)
--clusterSystem:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(targetItems, indexesToRemove[i]) end
--self.targetItems = targetItems
--g_currentMission:addMoney(totalPrice, ownerFarmId, MoneyType.SOLD_ANIMALS, true, true)
--self.targetBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_sellBulkResult"), totalSoldAnimals, g_i18n:formatMoney(math.abs(totalPrice), 2, true, true)), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.SELLING))
g_messageCenter:subscribe(AnimalSellEvent, self.onAnimalSold, self)
g_client:getServerConnection():sendEvent(AnimalSellEvent.new(husbandry, self.targetAnimals, totalPrice, totalTransportPrice))
if totalSoldAnimals == 1 then
self.husbandry:addRLMessage("SOLD_ANIMALS_SINGLE", nil, { g_i18n:formatMoney(totalPrice + totalTransportPrice, 2, true, true) })
elseif totalSoldAnimals > 0 then
self.husbandry:addRLMessage("SOLD_ANIMALS_MULTIPLE", nil, { totalSoldAnimals, g_i18n:formatMoney(totalPrice + totalTransportPrice, 2, true, true) })
end
end
================================================
FILE: src/animals/shop/controllers/AnimalScreenDealerTrailer.lua
================================================
RL_AnimalScreenDealerTrailer = {}
function RL_AnimalScreenDealerTrailer:initTargetItems(_)
self.targetItems = {}
local animals = self.trailer:getClusters()
if animals ~= nil then
for _, animal in pairs(animals) do
local item = AnimalItemStock.new(animal)
table.insert(self.targetItems, item)
end
end
table.sort(self.targetItems, RL_AnimalScreenBase.sortAnimals)
end
AnimalScreenDealerTrailer.initTargetItems = Utils.overwrittenFunction(AnimalScreenDealerTrailer.initTargetItems, RL_AnimalScreenDealerTrailer.initTargetItems)
function RL_AnimalScreenDealerTrailer:initSourceItems(_)
local animalSystem = g_currentMission.animalSystem
local animalType = self.trailer:getCurrentAnimalType()
if animalType == nil then
local animalTypes = animalSystem:getTypes()
self.sourceItems = {}
for _, type in pairs(animalTypes) do
local animalTypeIndex = type.typeIndex
if not self.trailer:getSupportsAnimalType(animalTypeIndex) then continue end
local animals = animalSystem:getSaleAnimalsByTypeIndex(animalTypeIndex)
for _, animal in pairs(animals) do
if self.sourceItems[animalTypeIndex] == nil then self.sourceItems[animalTypeIndex] = {} end
local item = AnimalItemNew.new(animal)
table.insert(self.sourceItems[animalTypeIndex], item)
end
if self.sourceItems[animalTypeIndex] ~= nil then table.sort(self.sourceItems[animalTypeIndex], RL_AnimalScreenBase.sortSaleAnimals) end
end
return
end
local animalTypeIndex = animalType.typeIndex
local animals = animalSystem:getSaleAnimalsByTypeIndex(animalTypeIndex)
self.sourceItems = { [animalTypeIndex] = {} }
for _, animal in pairs(animals) do
local item = AnimalItemNew.new(animal)
table.insert(self.sourceItems[animalTypeIndex], item)
end
table.sort(self.sourceItems[animalTypeIndex], RL_AnimalScreenBase.sortSaleAnimals)
end
AnimalScreenDealerTrailer.initSourceItems = Utils.overwrittenFunction(AnimalScreenDealerTrailer.initSourceItems, RL_AnimalScreenDealerTrailer.initSourceItems)
function RL_AnimalScreenDealerTrailer:getSourceAnimalTypes()
local currentAnimalType = self.trailer:getCurrentAnimalType()
if currentAnimalType ~= nil then return { currentAnimalType } end
local types = g_currentMission.animalSystem:getTypes()
local sourceTypes = {}
for _, type in ipairs(types) do
if self.trailer:getSupportsAnimalType(type.typeIndex) and self.sourceItems[type.typeIndex] ~= nil then table.insert(sourceTypes, type) end
end
return sourceTypes
end
AnimalScreenDealerTrailer.getSourceAnimalTypes = Utils.overwrittenFunction(AnimalScreenDealerTrailer.getSourceAnimalTypes, RL_AnimalScreenDealerTrailer.getSourceAnimalTypes)
function RL_AnimalScreenDealerTrailer:getSourceMaxNumAnimals(_, _)
return 1
end
AnimalScreenDealerTrailer.getSourceMaxNumAnimals = Utils.overwrittenFunction(AnimalScreenDealerTrailer.getSourceMaxNumAnimals, RL_AnimalScreenDealerTrailer.getSourceMaxNumAnimals)
function RL_AnimalScreenDealerTrailer:applySource(_, animalTypeIndex, animalIndex)
self.sourceAnimals = nil
local item = self.sourceItems[animalTypeIndex][animalIndex]
local trailer = self.trailer
local ownerFarmId = trailer:getOwnerFarmId()
local price = -item:getPrice()
local errorCode = AnimalBuyEvent.validate(trailer, item:getSubTypeIndex(), item:getAge(), 1, price, 0, ownerFarmId)
if errorCode ~= nil then
local error = AnimalScreenDealerFarm.BUY_ERROR_CODE_MAPPING[errorCode]
self.errorCallback(g_i18n:getText(error.text))
return false
end
--self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerTrailer.L10N_SYMBOL.BUYING))
local animal = item.animal or item.cluster
self.sourceAnimals = { animal }
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
g_messageCenter:subscribe(AnimalBuyEvent, self.onAnimalBought, self)
g_client:getServerConnection():sendEvent(AnimalBuyEvent.new(trailer, self.sourceAnimals, price, 0))
--trailer:getClusterSystem():addCluster(animal)
--g_currentMission:addMoney(price, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
--g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
--table.remove(self.sourceItems[animalTypeIndex], animalIndex)
--self.sourceActionFinished(nil, "Animal bought successfully")
return true
end
AnimalScreenDealerTrailer.applySource = Utils.overwrittenFunction(AnimalScreenDealerTrailer.applySource, RL_AnimalScreenDealerTrailer.applySource)
function RL_AnimalScreenDealerTrailer:applyTarget(_, _, animalIndex)
self.targetAnimals = nil
local item = self.targetItems[animalIndex]
local trailer = self.trailer
local ownerFarmId = trailer:getOwnerFarmId()
local price = item:getPrice()
local errorCode = AnimalSellEvent.validate(trailer, item:getSubTypeIndex(), item:getAge(), 1, price, 0, ownerFarmId)
if errorCode ~= nil then
local error = AnimalScreenDealerFarm.SELL_ERROR_CODE_MAPPING[errorCode]
self.errorCallback(g_i18n:getText(error.text))
return false
end
local animal = item.animal or item.cluster
self.targetAnimals = { animal }
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_TARGET, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.SELLING))
g_messageCenter:subscribe(AnimalSellEvent, self.onAnimalSold, self)
g_client:getServerConnection():sendEvent(AnimalSellEvent.new(trailer, self.targetAnimals, price, 0))
return true
end
AnimalScreenDealerTrailer.applyTarget = Utils.overwrittenFunction(AnimalScreenDealerTrailer.applyTarget, RL_AnimalScreenDealerTrailer.applyTarget)
function RL_AnimalScreenDealerTrailer:onAnimalBought(errorCode)
if errorCode == AnimalBuyEvent.BUY_SUCCESS and self.sourceAnimals ~= nil then
for _, animal in pairs(self.sourceAnimals) do g_currentMission.animalSystem:removeSaleAnimal(animal.animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId) end
end
end
AnimalScreenDealerTrailer.onAnimalBought = Utils.prependedFunction(AnimalScreenDealerTrailer.onAnimalBought, RL_AnimalScreenDealerTrailer.onAnimalBought)
function RL_AnimalScreenDealerTrailer:getSourcePrice(_, animalTypeIndex, animalIndex, _)
if self.sourceItems[animalTypeIndex] ~= nil then
local item = self.sourceItems[animalTypeIndex][animalIndex]
if item ~= nil then
local price = item:getPrice()
return true, price, 0, price
end
end
return false, 0, 0, 0
end
AnimalScreenDealerTrailer.getSourcePrice = Utils.overwrittenFunction(AnimalScreenDealerTrailer.getSourcePrice, RL_AnimalScreenDealerTrailer.getSourcePrice)
function AnimalScreenDealerTrailer:applySourceBulk(animalTypeIndex, items)
self.sourceAnimals = {}
local trailer = self.trailer
local clusterSystem = trailer:getClusterSystem()
local ownerFarmId = trailer:getOwnerFarmId()
local sourceItems = self.sourceItems[animalTypeIndex]
--local indexesToRemove = {}
--local indexesToReturn = {}
local totalPrice = 0
local totalBoughtAnimals = 0
for _, item in pairs(items) do
if sourceItems[item] ~= nil then
local sourceItem = sourceItems[item]
local animal = sourceItem.animal
local price = -sourceItem:getPrice()
local errorCode = AnimalBuyEvent.validate(trailer, animal.subTypeIndex, animal.age, 1, price, 0, ownerFarmId)
if errorCode ~= nil then continue end
totalBoughtAnimals = totalBoughtAnimals + 1
totalPrice = totalPrice + price
table.insert(self.sourceAnimals, animal)
--clusterSystem:addCluster(animal)
--g_currentMission.animalSystem:removeSaleAnimal(animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(sourceItems, indexesToRemove[i]) end
--self.sourceItems[animalTypeIndex] = sourceItems
--g_currentMission:addMoney(totalPrice, ownerFarmId, MoneyType.NEW_ANIMALS_COST, true, true)
--self.sourceBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_buyBulkResult"), totalBoughtAnimals, g_i18n:formatMoney(math.abs(totalPrice), 2, true, true)), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.BUYING))
g_messageCenter:subscribe(AnimalBuyEvent, self.onAnimalBought, self)
g_client:getServerConnection():sendEvent(AnimalBuyEvent.new(trailer, self.sourceAnimals, totalPrice, 0))
end
function AnimalScreenDealerTrailer:applyTargetBulk(animalTypeIndex, items)
self.targetAnimals = {}
local trailer = self.trailer
local clusterSystem = trailer:getClusterSystem()
local ownerFarmId = trailer:getOwnerFarmId()
local targetItems = self.targetItems
--local indexesToRemove = {}
--local indexesToReturn = {}
local totalPrice = 0
local totalSoldAnimals = 0
for _, item in pairs(items) do
if targetItems[item] ~= nil then
local targetItem = targetItems[item]
local animal = targetItem.animal or targetItem.cluster
local price = targetItem:getPrice()
local errorCode = AnimalSellEvent.validate(trailer, targetItem:getClusterId(), 1, price, 0)
if errorCode ~= nil then continue end
totalSoldAnimals = totalSoldAnimals + 1
totalPrice = totalPrice + price
table.insert(self.targetAnimals, animal)
--clusterSystem:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(targetItems, indexesToRemove[i]) end
--self.targetItems = targetItems
--g_currentMission:addMoney(totalPrice, ownerFarmId, MoneyType.SOLD_ANIMALS, true, true)
--self.targetBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_sellBulkResult"), totalSoldAnimals, g_i18n:formatMoney(math.abs(totalPrice), 2, true, true)), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenDealerFarm.L10N_SYMBOL.SELLING))
g_messageCenter:subscribe(AnimalSellEvent, self.onAnimalSold, self)
g_client:getServerConnection():sendEvent(AnimalSellEvent.new(trailer, self.targetAnimals, totalPrice, 0))
end
================================================
FILE: src/animals/shop/controllers/AnimalScreenTrailer.lua
================================================
RL_AnimalScreenTrailer = {}
function RL_AnimalScreenTrailer:initTargetItems(_)
self.targetItems = {}
local animals = self.trailer:getClusters()
if animals ~= nil then
for _, animal in pairs(animals) do
local item = AnimalItemStock.new(animal)
table.insert(self.targetItems, item)
end
end
table.sort(self.targetItems, RL_AnimalScreenBase.sortAnimals)
end
AnimalScreenTrailer.initTargetItems = Utils.overwrittenFunction(AnimalScreenTrailer.initTargetItems, RL_AnimalScreenTrailer.initTargetItems)
function RL_AnimalScreenTrailer:getApplySourceConfirmationText(_, animalTypeIndex, index, numAnimals)
--local text = numAnimals == 1 and g_i18n:getText(AnimalScreenTrailer.L10N_SYMBOL.CONFIRM_MOVE_TO_TRAILER_SINGULAR) or g_i18n:getText(AnimalScreenTrailer.L10N_SYMBOL.CONFIRM_MOVE_TO_TRAILER)
local text = "Do you want to move %d animals to the trailer?"
return string.format(text, numAnimals)
end
AnimalScreenTrailer.getApplySourceConfirmationText = Utils.overwrittenFunction(AnimalScreenTrailer.getApplySourceConfirmationText, RL_AnimalScreenTrailer.getApplySourceConfirmationText)
================================================
FILE: src/animals/shop/controllers/AnimalScreenTrailerFarm.lua
================================================
RL_AnimalScreenTrailerFarm = {}
function RL_AnimalScreenTrailerFarm:initTargetItems(_)
self.targetItems = {}
local animals = self.husbandry:getClusters()
if animals ~= nil then
for _, animal in pairs(animals) do
local item = AnimalItemStock.new(animal)
table.insert(self.targetItems, item)
end
end
table.sort(self.targetItems, RL_AnimalScreenBase.sortAnimals)
end
AnimalScreenTrailerFarm.initTargetItems = Utils.overwrittenFunction(AnimalScreenTrailerFarm.initTargetItems, RL_AnimalScreenTrailerFarm.initTargetItems)
function RL_AnimalScreenTrailerFarm:initSourceItems(_)
self.sourceItems = {}
local animalType = self.trailer:getCurrentAnimalType()
if animalType == nil then return end
local animals = self.trailer:getClusters()
if animals ~= nil then
for _, animal in pairs(animals) do
local item = AnimalItemStock.new(animal)
if self.sourceItems[animalType.typeIndex] == nil then self.sourceItems[animalType.typeIndex] = {} end
table.insert(self.sourceItems[animalType.typeIndex], item)
end
end
for _, category in pairs(self.sourceItems) do
table.sort(category, RL_AnimalScreenBase.sortAnimals)
end
end
AnimalScreenTrailerFarm.initSourceItems = Utils.overwrittenFunction(AnimalScreenTrailerFarm.initSourceItems, RL_AnimalScreenTrailerFarm.initSourceItems)
function AnimalScreenTrailerFarm:applySourceBulk(animalTypeIndex, items)
self.sourceAnimals = {}
local trailer = self.trailer
local husbandry = self.husbandry
local clusterSystemTrailer = trailer:getClusterSystem()
local clusterSystemHusbandry = husbandry:getClusterSystem()
local ownerFarmId = trailer:getOwnerFarmId()
local sourceItems = self.sourceItems[animalTypeIndex]
--local indexesToRemove = {}
--local indexesToReturn = {}
local totalMovedAnimals = 0
for _, item in pairs(items) do
if sourceItems[item] ~= nil then
local sourceItem = sourceItems[item]
local animal = sourceItem.animal or sourceItem.cluster
--local errorCode = AnimalMoveEvent.validate(trailer, husbandry, sourceItem:getClusterId(), 1, ownerFarmId)
local errorCode = AnimalMoveEvent.validate(trailer, husbandry, ownerFarmId, animal.subTypeIndex)
if errorCode ~= nil then continue end
totalMovedAnimals = totalMovedAnimals + 1
--clusterSystemTrailer:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--animal.id, animal.idFull = nil, nil
--clusterSystemHusbandry:addCluster(animal)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
table.insert(self.sourceAnimals, animal)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(sourceItems, indexesToRemove[i]) end
--self.sourceItems[animalTypeIndex] = sourceItems
--self.sourceBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_moveBulkResult"), totalMovedAnimals), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenTrailerFarm.L10N_SYMBOL.MOVE_TO_FARM))
g_messageCenter:subscribe(AnimalMoveEvent, self.onAnimalMovedToFarm, self)
g_client:getServerConnection():sendEvent(AnimalMoveEvent.new(trailer, husbandry, self.sourceAnimals, "TARGET"))
if totalMovedAnimals == 1 then
husbandry:addRLMessage("MOVED_ANIMALS_TARGET_SINGLE", nil, { trailer:getName() })
elseif totalMovedAnimals > 0 then
husbandry:addRLMessage("MOVED_ANIMALS_TARGET_MULTIPLE", nil, { totalMovedAnimals, trailer:getName() })
end
end
function AnimalScreenTrailerFarm:applyTargetBulk(animalTypeIndex, items)
self.targetAnimals = {}
local trailer = self.trailer
local husbandry = self.husbandry
local clusterSystemTrailer = trailer:getClusterSystem()
local clusterSystemHusbandry = husbandry:getClusterSystem()
local ownerFarmId = trailer:getOwnerFarmId()
local targetItems = self.targetItems
--local indexesToRemove = {}
--local indexesToReturn = {}
local totalMovedAnimals = 0
for _, item in pairs(items) do
if targetItems[item] ~= nil then
local targetItem = targetItems[item]
local animal = targetItem.animal or targetItem.cluster
--local errorCode = AnimalMoveEvent.validate(husbandry, trailer, targetItem:getClusterId(), 1, ownerFarmId)
local errorCode = AnimalMoveEvent.validate(husbandry, trailer, ownerFarmId, animal.subTypeIndex)
if errorCode ~= nil then continue end
totalMovedAnimals = totalMovedAnimals + 1
--clusterSystemHusbandry:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--animal.id, animal.idFull = nil, nil
--clusterSystemTrailer:addCluster(animal)
--table.insert(indexesToRemove, item)
--table.insert(indexesToReturn, item)
table.insert(self.targetAnimals, animal)
end
end
--table.sort(indexesToRemove)
--for i = #indexesToRemove, 1, -1 do table.remove(targetItems, indexesToRemove[i]) end
--self.targetItems = targetItems
--self.targetBulkActionFinished(nil, string.format(g_i18n:getText("rl_ui_moveBulkResult"), totalMovedAnimals), indexesToReturn)
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_TARGET, g_i18n:getText(AnimalScreenTrailerFarm.L10N_SYMBOL.MOVE_TO_TRAILER))
g_messageCenter:subscribe(AnimalMoveEvent, self.onAnimalMovedToTrailer, self)
g_client:getServerConnection():sendEvent(AnimalMoveEvent.new(husbandry, trailer, self.targetAnimals, "SOURCE"))
if totalMovedAnimals == 1 then
husbandry:addRLMessage("MOVED_ANIMALS_SOURCE_SINGLE", nil, { trailer:getName() })
elseif totalMovedAnimals > 0 then
husbandry:addRLMessage("MOVED_ANIMALS_SOURCE_MULTIPLE", nil, { totalMovedAnimals, trailer:getName() })
end
end
function RL_AnimalScreenTrailerFarm:applyTarget(_, _, animalIndex)
self.targetAnimals = nil
local trailer = self.trailer
local husbandry = self.husbandry
local clusterSystemTrailer = trailer:getClusterSystem()
local clusterSystemHusbandry = husbandry:getClusterSystem()
local ownerFarmId = trailer:getOwnerFarmId()
local item = self.targetItems[animalIndex]
local animal = item.animal or item.cluster
local id = item:getClusterId()
--local errorCode = AnimalMoveEvent.validate(husbandry, trailer, id, 1, ownerFarmId)
local errorCode = AnimalMoveEvent.validate(husbandry, trailer, ownerFarmId, animal.subTypeIndex)
if errorCode ~= nil then
self.errorCallback(g_i18n:getText(AnimalScreenTrailerFarm.MOVE_TO_TRAILER_ERROR_CODE_MAPPING[errorCode].text))
return false
end
self.targetAnimals = { animal }
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_TARGET, g_i18n:getText(AnimalScreenTrailerFarm.L10N_SYMBOL.MOVE_TO_TRAILER))
g_messageCenter:subscribe(AnimalMoveEvent, self.onAnimalMovedToTrailer, self)
g_client:getServerConnection():sendEvent(AnimalMoveEvent.new(husbandry, trailer, self.targetAnimals))
--clusterSystemHusbandry:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--animal.id, animal.idFull = nil, nil
--clusterSystemTrailer:addCluster(animal)
--table.remove(self.targetItems, animalIndex)
--self.targetActionFinished(false, g_i18n:getText(AnimalScreenTrailerFarm.MOVE_TO_TRAILER_ERROR_CODE_MAPPING[AnimalMoveEvent.MOVE_SUCCESS].text))
husbandry:addRLMessage("MOVED_ANIMALS_SOURCE_SINGLE", nil, { trailer:getName() })
return true
end
AnimalScreenTrailerFarm.applyTarget = Utils.overwrittenFunction(AnimalScreenTrailerFarm.applyTarget, RL_AnimalScreenTrailerFarm.applyTarget)
function RL_AnimalScreenTrailerFarm:applySource(_, animalTypeIndex, animalIndex)
self.sourceAnimals = nil
local trailer = self.trailer
local husbandry = self.husbandry
local clusterSystemTrailer = trailer:getClusterSystem()
local clusterSystemHusbandry = husbandry:getClusterSystem()
local ownerFarmId = trailer:getOwnerFarmId()
local sourceItems = self.sourceItems[animalTypeIndex]
local item = sourceItems[animalIndex]
local animal = item.animal or item.cluster
local id = item:getClusterId()
--local errorCode = AnimalMoveEvent.validate(trailer, husbandry, id, 1, ownerFarmId)
local errorCode = AnimalMoveEvent.validate(trailer, husbandry, ownerFarmId, animal.subTypeIndex)
if errorCode ~= nil then
self.errorCallback(g_i18n:getText(AnimalScreenTrailerFarm.MOVE_TO_FARM_ERROR_CODE_MAPPING[errorCode].text))
return false
end
self.sourceAnimals = { animal }
self.actionTypeCallback(AnimalScreenBase.ACTION_TYPE_SOURCE, g_i18n:getText(AnimalScreenTrailerFarm.L10N_SYMBOL.MOVE_TO_FARM))
g_messageCenter:subscribe(AnimalMoveEvent, self.onAnimalMovedToFarm, self)
g_client:getServerConnection():sendEvent(AnimalMoveEvent.new(trailer, husbandry, self.sourceAnimals))
--clusterSystemTrailer:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
--animal.id, animal.idFull = nil, nil
--clusterSystemHusbandry:addCluster(animal)
--table.remove(sourceItems, animalIndex)
--self.sourceActionFinished(false, g_i18n:getText(AnimalScreenTrailerFarm.MOVE_TO_FARM_ERROR_CODE_MAPPING[AnimalMoveEvent.MOVE_SUCCESS].text))
husbandry:addRLMessage("MOVED_ANIMALS_TARGET_SINGLE", nil, { trailer:getName() })
return true
end
AnimalScreenTrailerFarm.applySource = Utils.overwrittenFunction(AnimalScreenTrailerFarm.applySource, RL_AnimalScreenTrailerFarm.applySource)
================================================
FILE: src/animals/shop/events/AIAnimalBuyEvent.lua
================================================
AIAnimalBuyEvent = {}
local AIAnimalBuyEvent_mt = Class(AIAnimalBuyEvent, Event)
InitEventClass(AIAnimalBuyEvent, "AIAnimalBuyEvent")
function AIAnimalBuyEvent.emptyNew()
local self = Event.new(AIAnimalBuyEvent_mt)
return self
end
function AIAnimalBuyEvent.new(object, animals, price)
local event = AIAnimalBuyEvent.emptyNew()
event.object = object
event.animals = animals
event.price = price
return event
end
function AIAnimalBuyEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
local numAnimals = streamReadUInt16(streamId)
self.animals = {}
for i = 1, numAnimals do
local animal = Animal.new()
animal:readStream(streamId, connection)
table.insert(self.animals, animal)
end
self.price = streamReadFloat32(streamId)
self:run(connection)
end
function AIAnimalBuyEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
streamWriteUInt16(streamId, #self.animals)
for _, animal in pairs(self.animals) do animal:writeStream(streamId, connection) end
streamWriteFloat32(streamId, self.price)
end
function AIAnimalBuyEvent:run(connection)
for _, animal in pairs(self.animals) do
animal:setRecentlyBoughtByAI(true)
g_currentMission.animalSystem:removeSaleAnimal(animal.animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
end
self.object:addAnimals(self.animals)
if g_server ~= nil then
local farmId = self.object:getOwnerFarmId()
g_currentMission:addMoney(-self.price, farmId, MoneyType.NEW_ANIMALS_COST, true, true)
end
end
function AIAnimalBuyEvent.validate(object, numAnimals, price, farmId)
if object == nil then return AnimalBuyEvent.BUY_ERROR_OBJECT_DOES_NOT_EXIST end
if object:getNumOfFreeAnimalSlots() < numAnimals then return AnimalBuyEvent.BUY_ERROR_NOT_ENOUGH_SPACE end
if g_currentMission:getMoney(farmId) - price < 0 then return AnimalBuyEvent.BUY_ERROR_NOT_ENOUGH_MONEY end
return nil
end
================================================
FILE: src/animals/shop/events/AIAnimalInseminationEvent.lua
================================================
AIAnimalInseminationEvent = {}
local AIAnimalInseminationEvent_mt = Class(AIAnimalInseminationEvent, Event)
InitEventClass(AIAnimalInseminationEvent, "AIAnimalInseminationEvent")
function AIAnimalInseminationEvent.emptyNew()
local self = Event.new(AIAnimalInseminationEvent_mt)
return self
end
function AIAnimalInseminationEvent.new(object, items)
local event = AIAnimalInseminationEvent.emptyNew()
event.object = object
event.items = items
return event
end
function AIAnimalInseminationEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
local numItems = streamReadUInt16(streamId)
self.items = {}
for i = 1, numItems do
local identifiers = Animal.readStreamIdentifiers(streamId, connection)
local dewarUniqueId = streamReadString(streamId)
table.insert(self.items, { ["animal"] = identifiers, ["dewar"] = dewarUniqueId })
end
self:run(connection)
end
function AIAnimalInseminationEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
streamWriteUInt16(streamId, #self.items)
for _, item in pairs(self.items) do
item.animal:writeStreamIdentifiers(streamId, connection)
streamWriteString(item.dewar)
end
end
function AIAnimalInseminationEvent:run(connection)
local clusterSystem = self.object:getClusterSystem()
local farmId = self.object:getOwnerFarmId()
local farmDewars = g_dewarManager:getDewarsByFarm(farmId)
if farmDewars == nil then return end
for i, item in pairs(self.items) do
local dewars = farmDewars[item.animal.animalTypeIndex]
if dewars == nil or #dewars == 0 then continue end
local identifiers = item.animal
for _, dewar in pairs(dewars) do
if dewar:getUniqueId() == item.dewar then
for _, animal in pairs(clusterSystem.animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal:setInsemination(dewar.animal)
dewar:changeStraws(-1)
break
end
end
break
end
end
end
end
================================================
FILE: src/animals/shop/events/AIAnimalSellEvent.lua
================================================
AIAnimalSellEvent = {}
local AIAnimalSellEvent_mt = Class(AIAnimalSellEvent, Event)
InitEventClass(AIAnimalSellEvent, "AIAnimalSellEvent")
function AIAnimalSellEvent.emptyNew()
local self = Event.new(AIAnimalSellEvent_mt)
return self
end
function AIAnimalSellEvent.new(object, animals, price)
local event = AIAnimalSellEvent.emptyNew()
event.object = object
event.animals = animals
event.price = price
return event
end
function AIAnimalSellEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
local numAnimals = streamReadUInt16(streamId)
self.animals = {}
for i = 1, numAnimals do
local identifiers = Animal.readStreamIdentifiers(streamId, connection)
table.insert(self.animals, identifiers)
end
self.price = streamReadFloat32(streamId)
self:run(connection)
end
function AIAnimalSellEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
streamWriteUInt16(streamId, #self.animals)
for _, animal in pairs(self.animals) do animal:writeStreamIdentifiers(streamId, connection) end
streamWriteFloat32(streamId, self.price)
end
function AIAnimalSellEvent:run(connection)
local clusterSystem = self.object:getClusterSystem()
for i, identifier in pairs(self.animals) do
clusterSystem:removeCluster(identifier.farmId .. " " .. identifier.uniqueId .. " " .. (identifier.country or identifier.birthday.country))
end
if g_server ~= nil then
local farmId = self.object:getOwnerFarmId()
g_currentMission:addMoney(self.price, farmId, MoneyType.SOLD_ANIMALS, true, true)
end
end
function AIAnimalSellEvent.validate(object, numAnimals, price, farmId)
if object == nil then return AnimalSellEvent.SELL_ERROR_OBJECT_DOES_NOT_EXIST end
return nil
end
================================================
FILE: src/animals/shop/events/AIBulkMessageEvent.lua
================================================
AIBulkMessageEvent = {}
local AIBulkMessageEvent_mt = Class(AIBulkMessageEvent, Event)
InitEventClass(AIBulkMessageEvent, "AIBulkMessageEvent")
function AIBulkMessageEvent.emptyNew()
local self = Event.new(AIBulkMessageEvent_mt)
return self
end
function AIBulkMessageEvent.new(object, messages)
local event = AIBulkMessageEvent.emptyNew()
event.object = object
event.messages = messages
return event
end
function AIBulkMessageEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
local numMessages = streamReadUInt16(streamId)
self.messages = {}
for i = 1, numMessages do
local id = streamReadString(streamId)
local numArgs = streamReadUInt8(streamId)
local args = {}
for j = 1, numArgs do table.insert(args, streamReadString(streamId)) end
table.insert(self.messages, {
["id"] = id,
["args"] = args
})
end
self:run(connection)
end
function AIBulkMessageEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
streamWriteUInt16(streamId, #self.messages)
for i = 1, #self.messages do
local message = self.messages[i]
message.args = message.args or {}
streamWriteString(streamId, message.id)
streamWriteUInt8(streamId, #message.args)
for j = 1, #message.args do streamWriteString(streamId, message.args[j]) end
end
end
function AIBulkMessageEvent:run(connection)
for i = 1, #self.messages do
local message = self.messages[i]
self.object:addRLMessage(message.id, nil, message.args)
end
end
================================================
FILE: src/animals/shop/events/AnimalBuyEvent.lua
================================================
function AnimalBuyEvent.new(object, animals, buyPrice, transportPrice)
local event = AnimalBuyEvent.emptyNew()
event.object = object
event.animals = animals
event.buyPrice = buyPrice
event.transportPrice = transportPrice
return event
end
function AnimalBuyEvent:readStream(streamId, connection)
if connection:getIsServer() then
self.errorCode = streamReadUIntN(streamId, 3)
else
self.object = NetworkUtil.readNodeObject(streamId)
local numAnimals = streamReadUInt16(streamId)
self.animals = {}
for i = 1, numAnimals do
local animal = Animal.new()
animal:readStream(streamId, connection)
table.insert(self.animals, animal)
end
self.buyPrice = streamReadFloat32(streamId)
self.transportPrice = streamReadFloat32(streamId)
end
self:run(connection)
end
function AnimalBuyEvent:writeStream(streamId, connection)
if not connection:getIsServer() then
streamWriteUIntN(streamId, self.errorCode, 3)
return
end
NetworkUtil.writeNodeObject(streamId, self.object)
streamWriteUInt16(streamId, #self.animals)
for _, animal in pairs(self.animals) do animal:writeStream(streamId, connection) end
streamWriteFloat32(streamId, self.buyPrice)
streamWriteFloat32(streamId, self.transportPrice)
end
function AnimalBuyEvent:run(connection)
if connection:getIsServer() then
g_messageCenter:publish(AnimalBuyEvent, self.errorCode)
return
end
if not g_currentMission:getHasPlayerPermission("tradeAnimals", connection) then
connection:sendEvent(AnimalBuyEvent.newServerToClient(AnimalBuyEvent.BUY_ERROR_NO_PERMISSION))
return
end
local userId = g_currentMission.userManager:getUniqueUserIdByConnection(connection)
local farmId = g_farmManager:getFarmForUniqueUserId(userId).farmId
for _, animal in pairs(self.animals) do
local errorCode = AnimalBuyEvent.validate(self.object, animal.subTypeIndex, animal.age, #self.animals, self.buyPrice, self.transportPrice, farmId)
if errorCode ~= nil then
connection:sendEvent(AnimalBuyEvent.newServerToClient(errorCode))
return
end
end
for _, animal in pairs(self.animals) do
g_currentMission.animalSystem:removeSaleAnimal(animal.animalTypeIndex, animal.birthday.country, animal.farmId, animal.uniqueId)
end
self.object:addAnimals(self.animals)
g_currentMission:addMoney(self.buyPrice + self.transportPrice, farmId, MoneyType.NEW_ANIMALS_COST, true, true)
connection:sendEvent(AnimalBuyEvent.newServerToClient(AnimalBuyEvent.BUY_SUCCESS))
if g_server ~= nil and not g_server.netIsRunning then return end
if #self.animals == 1 then
self.object:addRLMessage("BOUGHT_ANIMALS_SINGLE", nil, { g_i18n:formatMoney(math.abs(self.buyPrice + self.transportPrice), 2, true, true) })
elseif #self.animals > 0 then
self.object:addRLMessage("BOUGHT_ANIMALS_MULTIPLE", nil, { #self.animals, g_i18n:formatMoney(math.abs(self.buyPrice + self.transportPrice), 2, true, true) })
end
end
================================================
FILE: src/animals/shop/events/AnimalInseminationEvent.lua
================================================
AnimalInseminationEvent = {}
local AnimalInseminationEvent_mt = Class(AnimalInseminationEvent, Event)
InitEventClass(AnimalInseminationEvent, "AnimalInseminationEvent")
function AnimalInseminationEvent.emptyNew()
local self = Event.new(AnimalInseminationEvent_mt)
return self
end
function AnimalInseminationEvent.new(object, animal, semen)
local event = AnimalInseminationEvent.emptyNew()
event.object = object
event.animal = animal
event.semen = semen
return event
end
function AnimalInseminationEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
self.animal = Animal.readStreamIdentifiers(streamId, connection)
self.semen = { ["genetics"] = {} }
semen.country = streamReadUInt8(streamId)
semen.farmId = streamReadString(streamId)
semen.uniqueId = streamReadString(streamId)
semen.name = streamReadString(streamId)
semen.typeIndex = streamReadUInt8(streamId)
semen.subTypeIndex = streamReadUInt8(streamId)
semen.success = streamReadFloat32(streamId)
semen.genetics.metabolism = streamReadFloat32(streamId)
semen.genetics.fertility = streamReadFloat32(streamId)
semen.genetics.health = streamReadFloat32(streamId)
semen.genetics.quality = streamReadFloat32(streamId)
semen.genetics.productivity = streamReadFloat32(streamId)
if semen.genetics.productivity < 0 then semen.genetics.productivity = nil end
self:run(connection)
end
function AnimalInseminationEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
self.animal:writeStreamIdentifiers(streamId, connection)
local semen = self.semen
streamWriteUInt8(streamId, semen.country)
streamWriteString(streamId, semen.farmId)
streamWriteString(streamId, semen.uniqueId)
streamWriteString(streamId, semen.name or "")
streamWriteUInt8(streamId, semen.typeIndex)
streamWriteUInt8(streamId, semen.subTypeIndex)
streamWriteFloat32(streamId, semen.success)
streamWriteFloat32(streamId, semen.genetics.metabolism)
streamWriteFloat32(streamId, semen.genetics.fertility)
streamWriteFloat32(streamId, semen.genetics.health)
streamWriteFloat32(streamId, semen.genetics.quality)
streamWriteFloat32(streamId, semen.genetics.productivity or -1)
end
function AnimalInseminationEvent:run(connection)
local clusterSystem = self.object:getClusterSystem()
local identifiers = self.animal
for _, animal in pairs(clusterSystem.animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal:setInsemination(self.semen)
break
end
end
end
================================================
FILE: src/animals/shop/events/AnimalInseminationResultEvent.lua
================================================
AnimalInseminationResultEvent = {}
local AnimalInseminationResultEvent_mt = Class(AnimalInseminationResultEvent, Event)
InitEventClass(AnimalInseminationResultEvent, "AnimalInseminationResultEvent")
function AnimalInseminationResultEvent.emptyNew()
local self = Event.new(AnimalInseminationResultEvent_mt)
return self
end
function AnimalInseminationResultEvent.new(object, animal, success)
local event = AnimalInseminationResultEvent.emptyNew()
event.object = object
event.animal = animal
event.success = success
return event
end
function AnimalInseminationResultEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
self.animal = Animal.readStreamIdentifiers(streamId, connection)
self.success = streamReadBool(streamId)
self:run(connection)
end
function AnimalInseminationResultEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
self.animal:writeStreamIdentifiers(streamId, connection)
streamWriteBool(streamId, self.success)
end
function AnimalInseminationResultEvent:run(connection)
if g_server ~= nil and not g_server.netIsRunning then return end
local clusterSystem = self.object:getClusterSystem()
local identifiers = self.animal
for _, animal in pairs(clusterSystem.animals) do
if animal.farmId == identifiers.farmId and animal.uniqueId == identifiers.uniqueId and animal.birthday.country == (identifiers.country or identifiers.birthday.country) then
animal:addRLMessage(string.format("INSEMINATION_%s", self.success and "SUCCESS" or "FAIL"))
return
end
end
end
================================================
FILE: src/animals/shop/events/AnimalMoveEvent.lua
================================================
function AnimalMoveEvent.new(sourceObject, targetObject, animals, moveType)
local event = AnimalMoveEvent.emptyNew()
event.sourceObject = sourceObject
event.targetObject = targetObject
event.animals = animals
event.moveType = moveType
return event
end
function AnimalMoveEvent:readStream(streamId, connection)
if connection:getIsServer() then
self.errorCode = streamReadUIntN(streamId, 3)
else
self.moveType = streamReadString(streamId)
self.sourceObject = NetworkUtil.readNodeObject(streamId)
self.targetObject = NetworkUtil.readNodeObject(streamId)
local numAnimals = streamReadUInt16(streamId)
self.animals = {}
for i = 1, numAnimals do
local animal = Animal.new()
local success = animal:readStream(streamId, connection)
table.insert(self.animals, animal)
end
end
self:run(connection)
end
function AnimalMoveEvent:writeStream(streamId, connection)
if not connection:getIsServer() then
streamWriteUIntN(streamId, self.errorCode, 3)
return
end
streamWriteString(streamId, self.moveType)
NetworkUtil.writeNodeObject(streamId, self.sourceObject)
NetworkUtil.writeNodeObject(streamId, self.targetObject)
streamWriteUInt16(streamId, #self.animals)
for _, animal in pairs(self.animals) do local success = animal:writeStream(streamId, connection) end
end
function AnimalMoveEvent:run(connection)
if connection:getIsServer() then
g_messageCenter:publish(AnimalMoveEvent, self.errorCode)
return
end
local userId = g_currentMission.userManager:getUniqueUserIdByConnection(connection)
local farmId = g_farmManager:getFarmForUniqueUserId(userId).farmId
for _, animal in pairs(self.animals) do
local errorCode = AnimalMoveEvent.validate(self.sourceObject, self.targetObject, farmId, animal.subTypeIndex)
if errorCode ~= nil then
connection:sendEvent(AnimalMoveEvent.newServerToClient(errorCode))
return
end
end
local clusterSystemSource = self.sourceObject:getClusterSystem()
local clusterSystemTarget = self.targetObject:getClusterSystem()
for _, animal in pairs(self.animals) do
clusterSystemSource:removeCluster(animal.farmId .. " " .. animal.uniqueId .. " " .. animal.birthday.country)
animal.id, animal.idFull = nil, nil
clusterSystemTarget:addCluster(animal)
end
connection:sendEvent(AnimalMoveEvent.newServerToClient(AnimalMoveEvent.MOVE_SUCCESS))
if g_server ~= nil and not g_server.netIsRunning then return end
local husbandry, trailer
if self.moveType == "SOURCE" then
husbandry, trailer = self.sourceObject, self.targetObject
else
husbandry, trailer = self.targetObject, self.sourceObject
end
if #self.animals == 1 then
husbandry:addRLMessage(string.format("MOVE_ANIMALS_%s_SINLGE", self.moveType), nil, { trailer:getName() })
elseif #self.animals > 0 then
husbandry:addRLMessage(string.format("MOVE_ANIMALS_%s_MULTIPLE", self.moveType), nil, { #self.animals, trailer:getName() })
end
end
function AnimalMoveEvent.validate(sourceObject, targetObject, farmId, subTypeIndex)
if sourceObject == nil then return AnimalMoveEvent.MOVE_ERROR_SOURCE_OBJECT_DOES_NOT_EXIST end
if targetObject == nil then return AnimalMoveEvent.MOVE_ERROR_TARGET_OBJECT_DOES_NOT_EXIST end
if not g_currentMission.accessHandler:canFarmAccess(farmId, sourceObject) or not g_currentMission.accessHandler:canFarmAccess(farmId, targetObject) then return AnimalMoveEvent.MOVE_ERROR_NO_PERMISSION end
if not targetObject:getSupportsAnimalSubType(subTypeIndex) then return AnimalMoveEvent.MOVE_ERROR_ANIMAL_NOT_SUPPORTED end
if targetObject:getNumOfFreeAnimalSlots(subTypeIndex) < 1 then return AnimalMoveEvent.MOVE_ERROR_NOT_ENOUGH_SPACE end
return nil
end
================================================
FILE: src/animals/shop/events/AnimalSellEvent.lua
================================================
function AnimalSellEvent.new(object, animals, price, transportPrice)
local event = AnimalSellEvent.emptyNew()
event.object = object
event.animals = animals
event.price = price
event.transportPrice = transportPrice
return event
end
function AnimalSellEvent:readStream(streamId, connection)
if connection:getIsServer() then
self.errorCode = streamReadUIntN(streamId, 3)
else
self.object = NetworkUtil.readNodeObject(streamId)
local numAnimals = streamReadUInt16(streamId)
self.animals = {}
for i = 1, numAnimals do
local identifiers = Animal.readStreamIdentifiers(streamId, connection)
table.insert(self.animals, identifiers)
end
self.price = streamReadFloat32(streamId)
self.transportPrice = streamReadFloat32(streamId)
end
self:run(connection)
end
function AnimalSellEvent:writeStream(streamId, connection)
if not connection:getIsServer() then
streamWriteUIntN(streamId, self.errorCode, 3)
return
end
NetworkUtil.writeNodeObject(streamId, self.object)
streamWriteUInt16(streamId, #self.animals)
for i, animal in pairs(self.animals) do
local success = animal:writeStreamIdentifiers(streamId, connection)
end
streamWriteFloat32(streamId, self.price)
streamWriteFloat32(streamId, self.transportPrice)
end
function AnimalSellEvent:run(connection)
if connection:getIsServer() then
g_messageCenter:publish(AnimalSellEvent, self.errorCode)
return
end
if not g_currentMission:getHasPlayerPermission("tradeAnimals", connection) then
connection:sendEvent(AnimalSellEvent.newServerToClient(AnimalSellEvent.SELL_ERROR_NO_PERMISSION))
return
end
local userId = g_currentMission.userManager:getUniqueUserIdByConnection(connection)
local farmId = g_farmManager:getFarmForUniqueUserId(userId).farmId
--for _, animal in pairs(self.animals) do
--local errorCode = AnimalSellEvent.validate(self.object, animal.subTypeIndex, animal.age, 1, self.price, self.transportPrice, farmId)
--if errorCode ~= nil then
--connection:sendEvent(AnimalSellEvent.newServerToClient(errorCode))
--return
--end
--end
local clusterSystem = self.object:getClusterSystem()
for i, identifier in pairs(self.animals) do
clusterSystem:removeCluster(identifier.farmId .. " " .. identifier.uniqueId .. " " .. (identifier.country or identifier.birthday.country))
end
g_currentMission:addMoney(self.price + self.transportPrice, farmId, MoneyType.SOLD_ANIMALS, true, true)
connection:sendEvent(AnimalSellEvent.newServerToClient(AnimalSellEvent.SELL_SUCCESS))
if g_server ~= nil and not g_server.netIsRunning then return end
if #self.animals == 1 then
self.object:addRLMessage("SOLD_ANIMALS_SINGLE", nil, { g_i18n:formatMoney(math.abs(self.price + self.transportPrice), 2, true, true) })
elseif #self.animals > 0 then
self.object:addRLMessage("SOLD_ANIMALS_MULTIPLE", nil, { #self.animals, g_i18n:formatMoney(math.abs(self.price + self.transportPrice), 2, true, true) })
end
end
================================================
FILE: src/animals/shop/events/RealisticLivestock_AnimalSellEvent.lua
================================================
RealisticLivestock_AnimalSellEvent = {}
function RealisticLivestock_AnimalSellEvent:run(connection)
if connection:getIsServer() then return end
if g_currentMission:getHasPlayerPermission("tradeAnimals", connection) then
local validate = AnimalSellEvent.validate(self.object, self.clusterId, self.numAnimals, self.sellPrice, self.feePrice)
if validate == nil then
local animal = self.object:getClusterById(self.clusterId)
animal.isSold = true
local spec = self.object.spec_husbandryAnimals
if animal.idFull ~= nil and animal.idFull ~= "1-1" and spec ~= nil then
local sep = string.find(animal.idFull, "-")
local husbandry = tonumber(string.sub(animal.idFull, 1, sep - 1))
local animalId = tonumber(string.sub(animal.idFull, sep + 1))
if husbandry == 0 or animalId == 0 then return end
removeHusbandryAnimal(husbandry, animalId)
local clusterHusbandry = spec.clusterHusbandry
clusterHusbandry.husbandryIdsToVisualAnimalCount[husbandry] = math.max(clusterHusbandry.husbandryIdsToVisualAnimalCount[husbandry] - 1, 0)
clusterHusbandry.visualAnimalCount = math.max(clusterHusbandry.visualAnimalCount - 1, 0)
for husbandryIndex, animalIds in pairs(clusterHusbandry.animalIdToCluster) do
if clusterHusbandry.husbandryIds[husbandryIndex] == husbandry then
table.remove(animalIds, animalId)
break
end
end
end
end
end
end
AnimalSellEvent.run = Utils.prependedFunction(AnimalSellEvent.run, RealisticLivestock_AnimalSellEvent.run)
================================================
FILE: src/animals/shop/events/SemenBuyEvent.lua
================================================
SemenBuyEvent = {}
local SemenBuyEvent_mt = Class(SemenBuyEvent, Event)
InitEventClass(SemenBuyEvent, "SemenBuyEvent")
function SemenBuyEvent.emptyNew()
local self = Event.new(SemenBuyEvent_mt)
return self
end
function SemenBuyEvent.new(animal, quantity, price, farmId, position, rotation)
local event = SemenBuyEvent.emptyNew()
event.animal = animal
event.quantity = quantity
event.price = price
event.farmId = farmId
event.position = position
event.rotation = rotation
return event
end
function SemenBuyEvent.newServerToClient(errorCode)
local event = SemenBuyEvent.emptyNew()
event.errorCode = errorCode
return event
end
function SemenBuyEvent:readStream(streamId, connection)
self.animal = Animal.new()
self.animal:readStream(streamId, connection)
self.animal.success = streamReadFloat32(streamId)
self.quantity = streamReadUInt16(streamId)
self.price = streamReadFloat32(streamId)
self.farmId = streamReadUInt8(streamId)
local x = streamReadFloat32(streamId)
local y = streamReadFloat32(streamId)
local z = streamReadFloat32(streamId)
local rx = streamReadFloat32(streamId)
local ry = streamReadFloat32(streamId)
local rz = streamReadFloat32(streamId)
self.position = { x, y, z }
self.rotation = { rx, ry, rz }
self:run(connection)
end
function SemenBuyEvent:writeStream(streamId, connection)
self.animal:writeStream(streamId, connection)
streamWriteFloat32(streamId, self.animal.success or 0.65)
streamWriteUInt16(streamId, self.quantity)
streamWriteFloat32(streamId, self.price)
streamWriteUInt8(streamId, self.farmId)
streamWriteFloat32(streamId, self.position[1])
streamWriteFloat32(streamId, self.position[2])
streamWriteFloat32(streamId, self.position[3])
streamWriteFloat32(streamId, self.rotation[1])
streamWriteFloat32(streamId, self.rotation[2])
streamWriteFloat32(streamId, self.rotation[3])
self:run(connection)
end
function SemenBuyEvent:run(connection)
local dewar = Dewar.new(g_currentMission:getIsServer(), g_currentMission:getIsClient())
dewar:setOwnerFarmId(self.farmId)
dewar:register(self.position, self.rotation, self.animal, self.quantity)
g_currentMission:addMoney(self.price, self.farmId, MoneyType.SEMEN_PURCHASE, true, true)
end
================================================
FILE: src/events/DewarManagerStateEvent.lua
================================================
DewarManagerStateEvent = {}
local DewarManagerStateEvent_mt = Class(DewarManagerStateEvent, Event)
InitEventClass(DewarManagerStateEvent, "DewarManagerStateEvent")
function DewarManagerStateEvent.emptyNew()
local self = Event.new(DewarManagerStateEvent_mt)
return self
end
function DewarManagerStateEvent.new()
local event = DewarManagerStateEvent.emptyNew()
return event
end
function DewarManagerStateEvent:readStream(streamId, connection)
g_dewarManager:readStream(streamId, connection)
self:run(connection)
end
function DewarManagerStateEvent:writeStream(streamId, connection)
g_dewarManager:writeStream(streamId, connection)
end
function DewarManagerStateEvent:run(connection)
end
================================================
FILE: src/events/HusbandryMessageStateEvent.lua
================================================
HusbandryMessageStateEvent = {}
local HusbandryMessageStateEvent_mt = Class(HusbandryMessageStateEvent, Event)
InitEventClass(HusbandryMessageStateEvent, "HusbandryMessageStateEvent")
function HusbandryMessageStateEvent.emptyNew()
local self = Event.new(HusbandryMessageStateEvent_mt)
return self
end
function HusbandryMessageStateEvent.new(husbandries)
local event = HusbandryMessageStateEvent.emptyNew()
event.husbandries = husbandries
return event
end
function HusbandryMessageStateEvent:readStream(streamId, connection)
local numHusbandries = streamReadUInt8(streamId)
for i = 1, numHusbandries do
local husbandry = NetworkUtil.readNodeObject(streamId)
local hasUnreadMessages = streamReadBool(streamId)
local nextUniqueId = streamReadUInt16(streamId)
husbandry:setHasUnreadRLMessages(hasUnreadMessages)
husbandry:setNextRLMessageUniqueId(nextUniqueId)
local numMessages = streamReadUInt16(streamId)
local messages = {}
for j = 1, numMessages do
local id = streamReadString(streamId)
local date = streamReadString(streamId)
local uniqueId = streamReadUInt16(streamId)
local message = {
["id"] = id,
["date"] = date,
["uniqueId"] = uniqueId,
["args"] = {}
}
local hasAnimal = streamReadBool(streamId)
if hasAnimal then message.animal = streamReadString(streamId) end
local numArgs = streamReadUInt8(streamId)
for k = 1, numArgs do table.insert(message.args, streamReadString(streamId)) end
table.insert(messages, message)
end
husbandry.spec_husbandryAnimals.messages = messages
end
end
function HusbandryMessageStateEvent:writeStream(streamId, connection)
streamWriteUInt8(streamId, #self.husbandries)
for _, husbandry in pairs(self.husbandries) do
NetworkUtil.writeNodeObject(streamId, husbandry)
streamWriteBool(streamId, husbandry:getHasUnreadRLMessages())
streamWriteUInt16(streamId, husbandry:getNextRLMessageUniqueId())
local messages = husbandry:getRLMessages()
streamWriteUInt16(streamId, #messages)
for i = 1, #messages do
local message = messages[i]
streamWriteString(streamId, message.id)
streamWriteString(streamId, message.date)
streamWriteUInt16(streamId, message.uniqueId)
streamWriteBool(streamId, message.animal ~= nil)
if message.animal ~= nil then streamWriteString(streamId, message.animal) end
streamWriteUInt8(streamId, #message.args)
for j = 1, #message.args do streamWriteString(streamId, message.args[j]) end
end
end
end
================================================
FILE: src/events/ReturnStrawEvent.lua
================================================
ReturnStrawEvent = {}
local ReturnStrawEvent_mt = Class(ReturnStrawEvent, Event)
InitEventClass(ReturnStrawEvent, "ReturnStrawEvent")
function ReturnStrawEvent.emptyNew()
local self = Event.new(ReturnStrawEvent_mt)
return self
end
function ReturnStrawEvent.new(object)
local event = ReturnStrawEvent.emptyNew()
event.object = object
return event
end
function ReturnStrawEvent:readStream(streamId, connection)
self.object = NetworkUtil.readNodeObject(streamId)
self:run(connection)
end
function ReturnStrawEvent:writeStream(streamId, connection)
NetworkUtil.writeNodeObject(streamId, self.object)
end
function ReturnStrawEvent:run(connection)
if self.object ~= nil then self.object:changeStraws(1) end
end
================================================
FILE: src/farms/FarmManager.lua
================================================
RL_FarmManager = {}
function RL_FarmManager:loadFromXMLFile(superFunc, path)
local returnValue = superFunc(self, path)
local animalSystem = g_currentMission.animalSystem
animalSystem:initialiseCountries()
if g_currentMission:getIsServer() then
local hasData = animalSystem:loadFromXMLFile()
animalSystem:validateFarms(hasData)
end
return returnValue
end
FarmManager.loadFromXMLFile = Utils.overwrittenFunction(FarmManager.loadFromXMLFile, RL_FarmManager.loadFromXMLFile)
================================================
FILE: src/farms/RealisticLivestock_FarmStats.lua
================================================
RealisticLivestock_FarmStats = {}
function RealisticLivestock_FarmStats:loadFromXMLFile(xmlFile, rootKey)
local key = rootKey .. ".statistics"
self.statistics.farmId = xmlFile:getInt(key .. ".farmId", math.random(100000, 999999))
self.statistics.cowId = xmlFile:getInt(key .. ".cowId", 0)
self.statistics.pigId = xmlFile:getInt(key .. ".pigId", 0)
self.statistics.sheepId = xmlFile:getInt(key .. ".sheepId", 0)
self.statistics.horseId = xmlFile:getInt(key .. ".horseId", 0)
self.statistics.chickenId = xmlFile:getInt(key .. ".chickenId", 0)
end
FarmStats.loadFromXMLFile = Utils.prependedFunction(FarmStats.loadFromXMLFile, RealisticLivestock_FarmStats.loadFromXMLFile)
function RealisticLivestock_FarmStats:saveToXMLFile(xmlFile, rootKey)
local key = rootKey .. ".statistics"
if self.statistics.farmId == nil then self.statistics.farmId = math.random(100000, 999999) end
if self.statistics.cowId == nil then self.statistics.cowId = 0 end
if self.statistics.pigId == nil then self.statistics.pigId = 0 end
if self.statistics.sheepId == nil then self.statistics.sheepId = 0 end
if self.statistics.horseId == nil then self.statistics.horseId = 0 end
if self.statistics.chickenId == nil then self.statistics.chickenId = 0 end
xmlFile:setInt(key .. ".farmId", self.statistics.farmId)
xmlFile:setInt(key .. ".cowId", self.statistics.cowId)
xmlFile:setInt(key .. ".pigId", self.statistics.pigId)
xmlFile:setInt(key .. ".sheepId", self.statistics.sheepId)
xmlFile:setInt(key .. ".horseId", self.statistics.horseId)
xmlFile:setInt(key .. ".chickenId", self.statistics.chickenId)
end
FarmStats.saveToXMLFile = Utils.prependedFunction(FarmStats.saveToXMLFile, RealisticLivestock_FarmStats.saveToXMLFile)
function RealisticLivestock_FarmStats:getNextAnimalId(animalType)
if animalType == AnimalType.COW then
if self.statistics.cowId == nil then self.statistics.cowId = 0 end
self.statistics.cowId = self.statistics.cowId + 1
return self.statistics.cowId
end
if animalType == AnimalType.PIG then
if self.statistics.pigId == nil then self.statistics.pigId = 0 end
self.statistics.pigId = self.statistics.pigId + 1
return self.statistics.pigId
end
if animalType == AnimalType.SHEEP then
if self.statistics.sheepId == nil then self.statistics.sheepId = 0 end
self.statistics.sheepId = self.statistics.sheepId + 1
return self.statistics.sheepId
end
if animalType == AnimalType.HORSE then
if self.statistics.horseId == nil then self.statistics.horseId = 0 end
self.statistics.horseId = self.statistics.horseId + 1
return self.statistics.horseId
end
if animalType == AnimalType.CHICKEN then
if self.statistics.chickenId == nil then self.statistics.chickenId = 0 end
self.statistics.chickenId = self.statistics.chickenId + 1
return self.statistics.chickenId
end
return 1
end
FarmStats.getNextAnimalId = RealisticLivestock_FarmStats.getNextAnimalId
================================================
FILE: src/fillTypes/RealisticLivestock_FillTypeManager.lua
================================================
RealisticLivestock_FillTypeManager = {}
local modDir = g_currentModDirectory
local modName = g_currentModName
if FillTypeManager.SEND_NUM_BITS < 10 then FillTypeManager.SEND_NUM_BITS = 10 end
function RealisticLivestock_FillTypeManager.loadFillTypes(xmlFile, missionInfo, baseDir)
local xml = loadXMLFile("fillTypes", modDir .. "xml/fillTypes.xml")
g_fillTypeManager:loadFillTypes(xml, modDir , false, modName)
end
FillTypeManager.loadMapData = Utils.appendedFunction(FillTypeManager.loadMapData, RealisticLivestock_FillTypeManager.loadFillTypes)
================================================
FILE: src/gui/AnimalAIDialog.lua
================================================
AnimalAIDialog = {}
local AnimalAIDialog_mt = Class(AnimalAIDialog, MessageDialog)
local modDirectory = g_currentModDirectory
function AnimalAIDialog.register()
local dialog = AnimalAIDialog.new()
g_gui:loadGui(modDirectory .. "gui/AnimalAIDialog.xml", "AnimalAIDialog", dialog)
AnimalAIDialog.INSTANCE = dialog
end
function AnimalAIDialog.new(target, customMt)
local dialog = MessageDialog.new(target, customMt or AnimalAIDialog_mt)
dialog.children = {}
return dialog
end
function AnimalAIDialog.createFromExistingGui(gui)
AnimalAIDialog.register()
AnimalAIDialog.show()
end
function AnimalAIDialog.show(farmId, animalTypeIndex, animal)
if AnimalAIDialog.INSTANCE == nil then AnimalAIDialog.register() end
local dialog = AnimalAIDialog.INSTANCE
dialog.farmId, dialog.animalTypeIndex, dialog.animal = farmId, animalTypeIndex, animal
dialog:updateDewars()
g_gui:showDialog("AnimalAIDialog")
end
function AnimalAIDialog:onCreate()
AnimalAIDialog:superClass().onCreate(self)
self:setDialogType(DialogElement.Type_INFO)
end
function AnimalAIDialog:onClickOk()
local farmDewars = g_dewarManager:getDewarsByFarm(self.farmId)
local selectedDewar = self.dewars[self.dewarList.selectedIndex]
if farmDewars == nil or farmDewars[self.animalTypeIndex] == nil or selectedDewar == nil then return end
local uniqueId = selectedDewar:getUniqueId()
for _, dewar in pairs(farmDewars[self.animalTypeIndex]) do
if dewar:getUniqueId() == uniqueId then
dewar:changeStraws(-1)
self.animal:setInsemination(dewar.animal)
break
end
end
self:updateDewars()
end
function AnimalAIDialog:onClickBack()
self:close()
end
function AnimalAIDialog:updateDewars()
local farmDewars = g_dewarManager:getDewarsByFarm(self.farmId)
self.dewars = farmDewars and table.clone(farmDewars[self.animalTypeIndex], 5) or {}
self:resetButtonStates()
self.dewarList:reloadData()
end
function AnimalAIDialog:onListSelectionChanged(list, index)
local dewar = self.dewars[index]
if dewar == nil then return end
self.okButton:setDisabled(not self.animal:getCanBeInseminatedByAnimal(dewar.animal))
end
function AnimalAIDialog:getNumberOfSections()
if self.dewars == nil or #self.dewars == 0 then return 0 end
return 1
end
function AnimalAIDialog:getNumberOfItemsInSection(list, section)
return self.dewars == nil and 0 or #self.dewars
end
function AnimalAIDialog:getTitleForSectionHeader(list, section)
return ""
end
function AnimalAIDialog:populateCellForItemInSection(list, section, index, cell)
local dewar = self.dewars[index]
if dewar == nil or dewar.animal == nil then return end
local animal = dewar.animal
local subType = g_currentMission.animalSystem:getSubTypeByIndex(animal.subTypeIndex)
cell:getAttribute("name"):setText(animal.name)
cell:getAttribute("identifier"):setText(string.format("%s %s %s", RealisticLivestock.AREA_CODES[animal.country].code, animal.farmId, animal.uniqueId))
cell:getAttribute("subType"):setText(g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex))
cell:getAttribute("straws"):setText(dewar.straws)
cell:getAttribute("success"):setText(string.format("%s%%", tostring(math.round(animal.success * 100))))
cell:getAttribute("productivity"):setText("N/A")
for type, value in pairs(animal.genetics) do
local valueText
if value >= 1.65 then
valueText = "extremelyHigh"
elseif value >= 1.4 then
valueText = "veryHigh"
elseif value >= 1.1 then
valueText = "high"
elseif value >= 0.9 then
valueText = "average"
elseif value >= 0.7 then
valueText = "low"
elseif value >= 0.35 then
valueText = "veryLow"
else
valueText = "extremelyLow"
end
cell:getAttribute(type):setText(g_i18n:getText("rl_ui_genetics_" .. valueText))
end
end
function AnimalAIDialog:resetButtonStates()
self.buttonStates = {
[self.nameButton] = { ["sorter"] = false, ["target"] = "animal|name", ["pos"] = "-5px" },
[self.identifierButton] = { ["sorter"] = false, ["target"] = "identifier", ["pos"] = "12px" },
[self.subTypeButton] = { ["sorter"] = false, ["target"] = "animal|subTypeIndex", ["pos"] = "35px" },
[self.strawsButton] = { ["sorter"] = false, ["target"] = "straws", ["pos"] = "12px" },
[self.successButton] = { ["sorter"] = false, ["target"] = "animal|success", ["pos"] = "12px" },
[self.metabolismButton] = { ["sorter"] = false, ["target"] = "animal|genetics|metabolism", ["pos"] = "22px" },
[self.qualityButton] = { ["sorter"] = false, ["target"] = "animal|genetics|quality", ["pos"] = "36px" },
[self.healthButton] = { ["sorter"] = false, ["target"] = "animal|genetics|health", ["pos"] = "36px" },
[self.fertilityButton] = { ["sorter"] = false, ["target"] = "animal|genetics|fertility", ["pos"] = "10px" },
[self.productivityButton] = { ["sorter"] = false, ["target"] = "animal|genetics|productivity", ["pos"] = "20px" }
}
self.sortingIcon_true:setVisible(false)
self.sortingIcon_false:setVisible(false)
end
function AnimalAIDialog:onClickSortButton(button)
local buttonState = self.buttonStates[button]
self["sortingIcon_" .. tostring(buttonState.sorter)]:setVisible(false)
self["sortingIcon_" .. tostring(not buttonState.sorter)]:setVisible(true)
self["sortingIcon_" .. tostring(not buttonState.sorter)]:setPosition(button.position[1] + GuiUtils.getNormalizedXValue(buttonState.pos), 0)
buttonState.sorter = not buttonState.sorter
local sorter = buttonState.sorter
local target = buttonState.target
local targetPaths = string.split(target, "|")
table.sort(self.dewars, function(a, b)
local aTarget, bTarget
if target == "identifier" then
aTarget = string.format("%s %s %s", RealisticLivestock.AREA_CODES[a.animal.country].code, a.animal.farmId, a.animal.uniqueId)
bTarget = string.format("%s %s %s", RealisticLivestock.AREA_CODES[b.animal.country].code, b.animal.farmId, b.animal.uniqueId)
else
aTarget = a[targetPaths[1]]
bTarget = b[targetPaths[1]]
for i = 2, #targetPaths do
aTarget = aTarget[targetPaths[i]]
bTarget = bTarget[targetPaths[i]]
end
end
if sorter then return aTarget > bTarget end
return aTarget < bTarget
end)
self.dewarList:reloadData()
end
================================================
FILE: src/gui/AnimalFilterDialog.lua
================================================
AnimalFilterDialog = {}
local animalFilterDialog_mt = Class(AnimalFilterDialog, MessageDialog)
local modDirectory = g_currentModDirectory
function AnimalFilterDialog.register()
local dialog = AnimalFilterDialog.new()
g_gui:loadGui(modDirectory .. "gui/AnimalFilterDialog.xml", "AnimalFilterDialog", dialog)
AnimalFilterDialog.INSTANCE = dialog
end
function AnimalFilterDialog.new(target, customMt)
local self = MessageDialog.new(target, customMt or animalFilterDialog_mt)
self.items = nil
self.filters = nil
self.elementsToDelete = {}
self.sliderTemplateOffset = GuiUtils.getNormalizedScreenValues("0px 45px")
self.binaryOptionTemplateOffset = GuiUtils.getNormalizedScreenValues("0px 30px")
return self
end
function AnimalFilterDialog.createFromExistingGui(gui)
AnimalFilterDialog.register()
AnimalFilterDialog.show()
end
function AnimalFilterDialog.show(items, animalTypeIndex, callback, target, isBuyMode)
if AnimalFilterDialog.INSTANCE == nil then AnimalFilterDialog.register() end
local dialog = AnimalFilterDialog.INSTANCE
dialog.items = table.clone(items)
dialog.animalTypeIndex = animalTypeIndex
dialog.callback = callback
dialog.target = target
dialog.isBuyMode = isBuyMode
g_gui:showDialog("AnimalFilterDialog")
end
function AnimalFilterDialog:onOpen()
AnimalFilterDialog:superClass().onOpen(self)
self.filters = {}
for i = #self.elementsToDelete, 1, -1 do
if self.elementsToDelete[i] ~= nil then self.elementsToDelete[i]:delete() end
table.remove(self.elementsToDelete, i)
end
local items = self.items
local anyText = g_i18n:getText("rl_ui_any")
local geneticsText = g_i18n:getText("rl_ui_genetics") .. ": "
local filters = {
{
["target"] = "age",
["name"] = g_i18n:getText("infohud_age"),
["template"] = "sliderTemplate",
["text"] = {
["single"] = g_i18n:getText("rl_ui_formatMonth"),
["multiple"] = g_i18n:getText("rl_ui_formatMonths")
},
["min"] = 0,
["max"] = 1
},
{
["target"] = "health",
["name"] = g_i18n:getText("infohud_health"),
["requiresMonitor"] = true,
["template"] = "sliderTemplate",
["text"] = {
["single"] = "%s%%",
["multiple"] = "%s%%"
},
["min"] = 0,
["max"] = 1
},
{
["target"] = "weight",
["name"] = g_i18n:getText("rl_ui_weight"),
["requiresMonitor"] = true,
["template"] = "sliderTemplate",
["text"] = {
["single"] = "%skg",
["multiple"] = "%skg"
},
["min"] = 0,
["max"] = 1
},
{
["target"] = "isPregnant",
["name"] = g_i18n:getText("rl_ui_pregnancy"),
["template"] = "binaryOptionTemplate",
["text"] = {
{
["text"] = g_i18n:getText("rl_ui_notPregnant"),
["value"] = false
},
{
["text"] = anyText,
["value"] = "ignore"
},
{
["text"] = g_i18n:getText("rl_ui_pregnant"),
["value"] = true
}
},
["default"] = 2
},
{
["target"] = "gender",
["name"] = g_i18n:getText("rl_ui_gender"),
["template"] = "binaryOptionTemplate",
["text"] = {
{
["text"] = g_i18n:getText("rl_ui_female"),
["value"] = "female"
},
{
["text"] = anyText,
["value"] = "ignore"
},
{
["text"] = g_i18n:getText("rl_ui_male"),
["value"] = "male"
}
},
["default"] = 2
},
{
["isFunction"] = true,
["target"] = "getHasAnyDisease",
["name"] = g_i18n:getText("rl_disease"),
["template"] = "binaryOptionTemplate",
["text"] = {
{
["text"] = g_i18n:getText("rl_ui_healthy"),
["value"] = false
},
{
["text"] = anyText,
["value"] = "ignore"
},
{
["text"] = g_i18n:getText("rl_ui_hasDisease"),
["value"] = true
}
},
["default"] = 1
},
{
["isFunction"] = true,
["target"] = "getHasName",
["name"] = g_i18n:getText("infohud_name"),
["template"] = "binaryOptionTemplate",
["text"] = {
{
["text"] = g_i18n:getText("rl_ui_doesntHaveName"),
["value"] = false
},
{
["text"] = anyText,
["value"] = "ignore"
},
{
["text"] = g_i18n:getText("rl_ui_doesHaveName"),
["value"] = true
}
},
["default"] = 2
},
{
["isFunction"] = true,
["target"] = "getSellPrice",
["name"] = g_i18n:getText("rl_ui_value"),
["template"] = "sliderTemplate",
["text"] = {
["formatFunction"] = I18N.formatMoney,
["target"] = g_i18n,
["args"] = {
"value",
2,
true,
true
}
},
["min"] = 0,
["max"] = 1,
["multiplier"] = self.isBuyMode and 1.075 or 1
},
{
["isLayered"] = true,
["target"] = {
"genetics",
"metabolism"
},
["name"] = geneticsText .. g_i18n:getText("rl_ui_metabolism"),
["template"] = "sliderTemplate",
["text"] = {
["formatFunction"] = AnimalFilterDialog.formatGenetics,
["target"] = self,
["args"] = {
"value"
}
},
["min"] = 0,
["max"] = 1,
["multiplier"] = 100
},
{
["isLayered"] = true,
["target"] = {
"genetics",
"health"
},
["name"] = geneticsText .. g_i18n:getText("rl_ui_health"),
["template"] = "sliderTemplate",
["text"] = {
["formatFunction"] = AnimalFilterDialog.formatGenetics,
["target"] = self,
["args"] = {
"value"
}
},
["min"] = 0,
["max"] = 1,
["multiplier"] = 100
},
{
["isLayered"] = true,
["target"] = {
"genetics",
"fertility"
},
["name"] = geneticsText .. g_i18n:getText("rl_ui_fertility"),
["template"] = "sliderTemplate",
["text"] = {
["formatFunction"] = AnimalFilterDialog.formatGenetics,
["target"] = self,
["args"] = {
"value"
}
},
["min"] = 0,
["max"] = 1,
["multiplier"] = 100
},
{
["isLayered"] = true,
["target"] = {
"genetics",
"quality"
},
["name"] = geneticsText .. g_i18n:getText("rl_ui_meat"),
["template"] = "sliderTemplate",
["text"] = {
["formatFunction"] = AnimalFilterDialog.formatGenetics,
["target"] = self,
["args"] = {
"value"
}
},
["min"] = 0,
["max"] = 1,
["multiplier"] = 100
}
}
if self.animalTypeIndex == AnimalType.COW or self.animalTypeIndex == AnimalType.SHEEP or self.animalTypeIndex == AnimalType.CHICKEN then
table.insert(filters, {
["isLayered"] = true,
["target"] = {
"genetics",
"productivity"
},
["name"] = geneticsText .. g_i18n:getText("statistic_productivity"),
["template"] = "sliderTemplate",
["text"] = {
["formatFunction"] = AnimalFilterDialog.formatGenetics,
["target"] = self,
["args"] = {
"value"
}
},
["min"] = 0,
["max"] = 1,
["multiplier"] = 100
})
end
if self.animalTypeIndex == AnimalType.COW then
table.insert(filters, 6, {
["target"] = "isLactating",
["name"] = g_i18n:getText("rl_ui_lactating"),
["template"] = "binaryOptionTemplate",
["text"] = {
{
["text"] = g_i18n:getText("rl_ui_no"),
["value"] = false
},
{
["text"] = anyText,
["value"] = "ignore"
},
{
["text"] = g_i18n:getText("rl_ui_yes"),
["value"] = true
}
},
["default"] = 2
})
end
for _, item in pairs(items) do
local animal = item.animal or item.cluster
for _, filter in pairs(filters) do
if (filter.requiresMonitor and not animal.monitor.active and not animal.monitor.removed) or filter.template ~= "sliderTemplate" then continue end
local value
if filter.isLayered then
value = animal
for _, target in pairs(filter.target) do
value = value[target]
end
elseif filter.isFunction then
value = animal[filter.target](animal)
else
value = animal[filter.target]
end
if value < filter.min then filter.min = math.floor(value) end
if value > filter.max then filter.max = math.ceil(value) end
filter.hasValues = true
end
end
for i = #filters, 1, -1 do
if filters[i].template == "sliderTemplate" and not filters[i].hasValues then table.remove(filters, i) end
end
self.filters = filters
self.filterList:reloadData()
end
function AnimalFilterDialog:onClose()
AnimalFilterDialog:superClass().onClose(self)
end
function AnimalFilterDialog:onClickOk()
for i = #self.filters, 1, -1 do
local filter = self.filters[i]
local element = self.elementsToDelete[i]
if filter.template == "sliderTemplate" and element ~= nil then
local multiplier = filter.multiplier or 1
filter.min, filter.max = (element:getLowestState() - 1) / multiplier , (element:getHighestState() - 1) / multiplier
end
if filter.template == "binaryOptionTemplate" then
local state = element == nil and (filter.default or 1) or element:getState()
local value = filter.text[state].value
if value == "ignore" then
table.remove(self.filters, i)
continue
end
filter.value = value
end
end
for i = #self.items, 1, -1 do
local item = self.items[i]
local animal = item.animal or item.cluster
local meetsFilters = true
for _, filter in pairs(self.filters) do
if filter.requiresMonitor and not animal.monitor.active and not animal.monitor.removed then continue end
if filter.template == "sliderTemplate" then
local value
if filter.isLayered then
value = animal
for _, target in pairs(filter.target) do value = value[target] end
elseif filter.isFunction then
value = animal[filter.target](animal)
if filter.name == "Value" and self.isBuyMode then value = value * 1.075 end
else
value = animal[filter.target]
end
if value < filter.min or value > filter.max then
meetsFilters = false
break
end
end
if filter.template == "binaryOptionTemplate" then
local Value
if filter.isFunction then
value = animal[filter.target](animal)
else
value = animal[filter.target]
end
if value ~= filter.value then
meetsFilters = false
break
end
end
end
self.items[i].originalIndex = i
if not meetsFilters then table.remove(self.items, i) end
end
if self.callback ~= nil then
if self.target ~= nil then
self.callback(self.target, self.filters, self.items)
else
self.callback(self.filters, self.items)
end
end
self:close()
end
function AnimalFilterDialog:getNumberOfSections()
return 1
end
function AnimalFilterDialog:getNumberOfItemsInSection(list, section)
return #self.filters
end
function AnimalFilterDialog:getTitleForSectionHeader(list, section)
return ""
end
function AnimalFilterDialog:populateCellForItemInSection(list, section, index, cell)
local filter = self.filters[index]
cell:findAllAttributes()
cell:getAttribute("name"):setText(filter.name)
if filter.template ~= nil then
if self.elementsToDelete[index] ~= nil then
local oldTemplate = self.elementsToDelete[index]
local template = self[filter.template]:clone(cell, false, false)
local texts = table.clone(oldTemplate.texts)
template:setTexts(texts)
template:setPosition(self[filter.template .. "Offset"][1], self[filter.template .. "Offset"][2])
template:setVisible(true)
if filter.template == "sliderTemplate" then
template.leftState = oldTemplate.leftState
template.rightState = oldTemplate.rightState
template:updateContentElement()
template:updateSlider()
end
if filter.template == "binaryOptionTemplate" then
template:setState(oldTemplate:getState(), false, true)
end
oldTemplate:delete()
self.elementsToDelete[index] = template
else
local template = self[filter.template]:clone(cell, false, false)
local templateTexts = {}
if filter.template == "sliderTemplate" then
local multiplier = filter.multiplier or 1
for i = filter.min * multiplier, filter.max * multiplier do
if filter.text.formatFunction ~= nil then
local args = table.clone(filter.text.args or {})
for argIndex, arg in pairs(args) do if arg == "value" then args[argIndex] = i end end
local text = filter.text.formatFunction(filter.text.target, args[1], args[2], args[3], args[4])
table.insert(templateTexts, text)
else
table.insert(templateTexts, string.format(filter.text[i == 1 and "single" or "multiple"], i))
end
end
end
if filter.template == "binaryOptionTemplate" then
for _, data in pairs(filter.text) do table.insert(templateTexts, data.text) end
end
template:setTexts(templateTexts)
template:setPosition(self[filter.template .. "Offset"][1], self[filter.template .. "Offset"][2])
template:setVisible(true)
if filter.template == "binaryOptionTemplate" then template:setState(filter.default or 1) end
self.elementsToDelete[index] = template
end
end
for name, element in pairs(cell.attributes) do
if name ~= "name" and name ~= "separator" then element:delete() end
end
end
function AnimalFilterDialog:formatGenetics(value)
local text
if value >= 165 then
text = "extremelyHigh"
elseif value >= 140 then
text = "veryHigh"
elseif value >= 110 then
text = "high"
elseif value >= 90 then
text = "average"
elseif value >= 70 then
text = "low"
elseif value >= 35 then
text = "veryLow"
else
text = "extremelyLow"
end
return g_i18n:getText("rl_ui_genetics_" .. text)
end
================================================
FILE: src/gui/AnimalInfoDialog.lua
================================================
AnimalInfoDialog = {}
local animalInfoDialog_mt = Class(AnimalInfoDialog, MessageDialog)
local modDirectory = g_currentModDirectory
function AnimalInfoDialog.register()
local dialog = AnimalInfoDialog.new()
g_gui:loadGui(modDirectory .. "gui/AnimalInfoDialog.xml", "AnimalInfoDialog", dialog)
AnimalInfoDialog.INSTANCE = dialog
end
function AnimalInfoDialog.new(target, customMt)
local dialog = MessageDialog.new(target, customMt or animalInfoDialog_mt)
dialog.children = {}
return dialog
end
function AnimalInfoDialog.createFromExistingGui(gui)
AnimalInfoDialog.register()
AnimalInfoDialog.show()
end
function AnimalInfoDialog.show(farmId, uniqueId, children, animalType, identifiers)
if AnimalInfoDialog.INSTANCE == nil then AnimalInfoDialog.register() end
local dialog = AnimalInfoDialog.INSTANCE
dialog.identifiers = identifiers
dialog.animalType = animalType
dialog:setDialogType(DialogElement.TYPE_INFO)
dialog.children = children or {}
dialog:setChildren(children or {})
local success = dialog:updateContent(farmId, uniqueId, children ~= nil and #children > 0)
if success then g_gui:showDialog("AnimalInfoDialog") end
end
function AnimalInfoDialog:onOpen()
AnimalInfoDialog:superClass().onOpen(self)
FocusManager:setFocus(self.dialogTextElement)
end
function AnimalInfoDialog:onClose()
self.children = {}
AnimalInfoDialog:superClass().onClose(self)
end
function AnimalInfoDialog:onCreate()
AnimalInfoDialog:superClass().onCreate(self)
self:setDialogType(DialogElement.Type_INFO)
end
function AnimalInfoDialog:onClickOk()
self:close()
end
function AnimalInfoDialog:setTexts(baseStatsTexts, advancedStatsTexts, horseStatsTexts, geneticsStatsTexts)
for i=10, #self.infoTitle do
self.infoTitle[i]:setVisible(false)
self.infoValue[i]:setVisible(false)
self.infoTitle[i]:setTextColor(0.89627, 0.92158, 0.81485, 1)
self.infoValue[i]:setTextColor(0.89627, 0.92158, 0.81485, 1)
end
self.separator1:setVisible(#advancedStatsTexts > 0 or #horseStatsTexts > 0 or #geneticsStatsTexts > 0)
self.separator2:setVisible(#advancedStatsTexts > 0 and (#horseStatsTexts > 0 or #geneticsStatsTexts > 0))
self.separator3:setVisible(#advancedStatsTexts > 0 and #horseStatsTexts > 0 and #geneticsStatsTexts > 0)
for i=1, 9 do
self.infoTitle[i]:setVisible(baseStatsTexts[i] ~= nil)
self.infoValue[i]:setVisible(baseStatsTexts[i] ~= nil)
if baseStatsTexts[i] ~= nil then
self.infoTitle[i]:setText(baseStatsTexts[i].title)
self.infoValue[i]:setText(baseStatsTexts[i].text)
end
end
local k = 1
if #advancedStatsTexts > 0 then
for i=10, 15 do
self.infoTitle[i]:setVisible(advancedStatsTexts[k] ~= nil)
self.infoValue[i]:setVisible(advancedStatsTexts[k] ~= nil)
if advancedStatsTexts[k] ~= nil then
self.infoTitle[i]:setText(advancedStatsTexts[k].title)
self.infoValue[i]:setText(advancedStatsTexts[k].text)
end
k = k + 1
end
elseif #horseStatsTexts > 0 then
for i=10, 15 do
self.infoTitle[i]:setVisible(horseStatsTexts[k] ~= nil)
self.infoValue[i]:setVisible(horseStatsTexts[k] ~= nil)
if horseStatsTexts[k] ~= nil then
self.infoTitle[i]:setText(horseStatsTexts[k].title)
self.infoValue[i]:setText(horseStatsTexts[k].text)
end
k = k + 1
end
elseif #geneticsStatsTexts > 0 then
for i=10, 15 do
self.infoTitle[i]:setVisible(geneticsStatsTexts[k] ~= nil)
self.infoValue[i]:setVisible(geneticsStatsTexts[k] ~= nil)
if geneticsStatsTexts[k] ~= nil then
self.infoTitle[i]:setText(geneticsStatsTexts[k].title)
self.infoValue[i]:setText(g_i18n:getText(geneticsStatsTexts[k].text))
local quality = geneticsStatsTexts[k].text
if quality == "rl_ui_genetics_extremelyLow" or quality == "rl_ui_genetics_extremelyBad" then
self.infoTitle[i]:setTextColor(1, 0, 0, 1)
self.infoValue[i]:setTextColor(1, 0, 0, 1)
elseif quality == "rl_ui_genetics_veryLow" or quality == "rl_ui_genetics_veryBad" then
self.infoTitle[i]:setTextColor(1, 0.2, 0, 1)
self.infoValue[i]:setTextColor(1, 0.2, 0, 1)
elseif quality == "rl_ui_genetics_low" or quality == "rl_ui_genetics_bad" then
self.infoTitle[i]:setTextColor(1, 0.52, 0, 1)
self.infoValue[i]:setTextColor(1, 0.52, 0, 1)
elseif quality == "rl_ui_genetics_average" then
self.infoTitle[i]:setTextColor(1, 1, 0, 1)
self.infoValue[i]:setTextColor(1, 1, 0, 1)
elseif quality == "rl_ui_genetics_high" or quality == "rl_ui_genetics_good" then
self.infoTitle[i]:setTextColor(0.52, 1, 0, 1)
self.infoValue[i]:setTextColor(0.52, 1, 0, 1)
elseif quality == "rl_ui_genetics_veryHigh" or quality == "rl_ui_genetics_veryGood" then
self.infoTitle[i]:setTextColor(0.2, 1, 0, 1)
self.infoValue[i]:setTextColor(0.2, 1, 0, 1)
else
self.infoTitle[i]:setTextColor(0, 1, 0, 1)
self.infoValue[i]:setTextColor(0, 1, 0, 1)
end
end
k = k + 1
end
end
k = 1
if #advancedStatsTexts > 0 then
if #horseStatsTexts > 0 then
for i=16, 21 do
self.infoTitle[i]:setVisible(horseStatsTexts[k] ~= nil)
self.infoValue[i]:setVisible(horseStatsTexts[k] ~= nil)
if horseStatsTexts[k] ~= nil then
self.infoTitle[i]:setText(horseStatsTexts[k].title)
self.infoValue[i]:setText(horseStatsTexts[k].text)
end
k = k + 1
end
elseif #geneticsStatsTexts > 0 then
for i=16, 21 do
self.infoTitle[i]:setVisible(geneticsStatsTexts[k] ~= nil)
self.infoValue[i]:setVisible(geneticsStatsTexts[k] ~= nil)
if geneticsStatsTexts[k] ~= nil then
self.infoTitle[i]:setText(geneticsStatsTexts[k].title)
self.infoValue[i]:setText(g_i18n:getText(geneticsStatsTexts[k].text))
local quality = geneticsStatsTexts[k].text
if quality == "rl_ui_genetics_extremelyLow" or quality == "rl_ui_genetics_extremelyBad" then
self.infoTitle[i]:setTextColor(1, 0, 0, 1)
self.infoValue[i]:setTextColor(1, 0, 0, 1)
elseif quality == "rl_ui_genetics_veryLow" or quality == "rl_ui_genetics_veryBad" then
self.infoTitle[i]:setTextColor(1, 0.2, 0, 1)
self.infoValue[i]:setTextColor(1, 0.2, 0, 1)
elseif quality == "rl_ui_genetics_low" or quality == "rl_ui_genetics_bad" then
self.infoTitle[i]:setTextColor(1, 0.52, 0, 1)
self.infoValue[i]:setTextColor(1, 0.52, 0, 1)
elseif quality == "rl_ui_genetics_average" then
self.infoTitle[i]:setTextColor(1, 1, 0, 1)
self.infoValue[i]:setTextColor(1, 1, 0, 1)
elseif quality == "rl_ui_genetics_high" or quality == "rl_ui_genetics_good" then
self.infoTitle[i]:setTextColor(0.52, 1, 0, 1)
self.infoValue[i]:setTextColor(0.52, 1, 0, 1)
elseif quality == "rl_ui_genetics_veryHigh" or quality == "rl_ui_genetics_veryGood" then
self.infoTitle[i]:setTextColor(0.2, 1, 0, 1)
self.infoValue[i]:setTextColor(0.2, 1, 0, 1)
else
self.infoTitle[i]:setTextColor(0, 1, 0, 1)
self.infoValue[i]:setTextColor(0, 1, 0, 1)
end
end
k = k + 1
end
end
end
k = 1
if #advancedStatsTexts > 0 and #horseStatsTexts > 0 then
for i=22, 27 do
self.infoTitle[i]:setVisible(geneticsStatsTexts[k] ~= nil)
self.infoValue[i]:setVisible(geneticsStatsTexts[k] ~= nil)
if geneticsStatsTexts[k] ~= nil then
self.infoTitle[i]:setText(geneticsStatsTexts[k].title)
self.infoValue[i]:setText(g_i18n:getText(geneticsStatsTexts[k].text))
local quality = geneticsStatsTexts[k].text
if quality == "rl_ui_genetics_extremelyLow" or quality == "rl_ui_genetics_extremelyBad" then
self.infoTitle[i]:setTextColor(1, 0, 0, 1)
self.infoValue[i]:setTextColor(1, 0, 0, 1)
elseif quality == "rl_ui_genetics_veryLow" or quality == "rl_ui_genetics_veryBad" then
self.infoTitle[i]:setTextColor(1, 0.2, 0, 1)
self.infoValue[i]:setTextColor(1, 0.2, 0, 1)
elseif quality == "rl_ui_genetics_low" or quality == "rl_ui_genetics_bad" then
self.infoTitle[i]:setTextColor(1, 0.52, 0, 1)
self.infoValue[i]:setTextColor(1, 0.52, 0, 1)
elseif quality == "rl_ui_genetics_average" then
self.infoTitle[i]:setTextColor(1, 1, 0, 1)
self.infoValue[i]:setTextColor(1, 1, 0, 1)
elseif quality == "rl_ui_genetics_high" or quality == "rl_ui_genetics_good" then
self.infoTitle[i]:setTextColor(0.52, 1, 0, 1)
self.infoValue[i]:setTextColor(0.52, 1, 0, 1)
elseif quality == "rl_ui_genetics_veryHigh" or quality == "rl_ui_genetics_veryGood" then
self.infoTitle[i]:setTextColor(0.2, 1, 0, 1)
self.infoValue[i]:setTextColor(0.2, 1, 0, 1)
else
self.infoTitle[i]:setTextColor(0, 1, 0, 1)
self.infoValue[i]:setTextColor(0, 1, 0, 1)
end
end
k = k + 1
end
end
end
function AnimalInfoDialog:setChildren(children)
local texts = {}
local foundChildren = {}
local placeables = g_currentMission.placeableSystem.placeables
for _, placeable in ipairs(placeables) do
if placeable.spec_husbandryAnimals == nil and placeable.spec_livestockTrailer == nil then continue end
local clusterSystem = nil
if placeable.spec_husbandryAnimals ~= nil then
if placeable.spec_husbandryAnimals.animalTypeIndex ~= self.animalType then continue end
clusterSystem = placeable.spec_husbandryAnimals.clusterSystem
elseif placeable.spec_livestockTrailer ~= nil then
clusterSystem = placeable.spec_livestockTrailer.clusterSystem
end
if clusterSystem == nil then continue end
local animals = clusterSystem:getAnimals()
for _, animal in ipairs(animals) do
for _, child in ipairs(children) do
if child.farmId == animal.farmId and child.uniqueId == animal.uniqueId then
table.insert(foundChildren, animal)
break
end
end
end
end
self.children = foundChildren
for _, child in ipairs(foundChildren) do
table.insert(texts, child.farmId .. " " .. child.uniqueId)
end
self.childrenSelector:setTexts(texts or {})
self.childrenSelector:setVisible(foundChildren ~= nil and #foundChildren > 0)
if foundChildren ~= nil and #foundChildren > 0 then self.childrenSelector:setState(1) end
end
function AnimalInfoDialog:onClickItems(index, _, _)
if self.children == nil or #self.children == 0 or self.children[index] == nil then return end
self:updateContent(self.children[index].farmId, self.children[index].uniqueId, true)
end
function AnimalInfoDialog:updateContent(farmId, uniqueId, useChildren)
if farmId == nil or uniqueId == nil then return false end
local parent = nil
if useChildren then
for _, child in ipairs(self.children) do
if child.farmId == farmId and child.uniqueId == uniqueId then
parent = child
break
end
end
else
local foundAnimals = {}
local placeables = g_currentMission.placeableSystem.placeables
for _, placeable in ipairs(placeables) do
if placeable.spec_husbandryAnimals == nil and placeable.spec_livestockTrailer == nil then continue end
local clusterSystem = nil
if placeable.spec_husbandryAnimals ~= nil then
if placeable.spec_husbandryAnimals.animalTypeIndex ~= self.animalType then continue end
clusterSystem = placeable.spec_husbandryAnimals.clusterSystem
elseif placeable.spec_livestockTrailer ~= nil then
clusterSystem = placeable.spec_livestockTrailer.clusterSystem
end
if clusterSystem == nil then continue end
local animals = clusterSystem:getAnimals()
for _, animal in ipairs(animals) do
if self.children ~= nil and #self.children > 1 and (animal.farmId ~= farmId or animal.uniqueId ~= uniqueId) then
for _, child in ipairs(self.children) do
if child.farmId == animal.farmId and child.uniqueId == animal.uniqueId then
table.insert(foundAnimals, animal)
break
end
end
end
if animal.farmId ~= farmId or animal.uniqueId ~= uniqueId then continue end
--parent = animal
table.insert(foundAnimals, animal)
if self.children == nil or #self.children <= 1 then break end
end
--if parent ~= nil then break end
if (self.children == nil or #self.children <= 1) and #foundAnimals >= 1 then
parent = foundAnimals[1]
break
end
end
if parent == nil then
--if self.children ~= nil and #self.children > 1 and self.childrenSelector:getState() < #self.children then
--local index = self.childrenSelector:getState() + 1
--self.childrenSelector:setState(index)
--self:updateContent(self.children[index].farmId, self.children[index].uniqueId)
--else
--InfoDialog.INSTANCE:setText("Could not find animal")
--g_gui:showDialog("InfoDialog")
--return false
--end
if self.children ~= nil and #self.children > 1 and #foundAnimals >= 1 then
local index = self.childrenSelector:getState()
while index <= #self.children do
for _, child in ipairs(foundAnimals) do
if child.farmId == self.children[index].farmId and child.uniqueId == self.children[index].uniqueId then
parent = child
self.childrenSelector:setState(index)
break
end
end
if parent ~= nil then break end
index = index + 1
end
end
end
end
if parent == nil then
InfoDialog.INSTANCE:setText(g_i18n:getText("rl_ui_cantFindAnimal"))
g_gui:showDialog("InfoDialog")
return false
end
local visual = g_currentMission.animalSystem:getVisualByAge(parent:getSubTypeIndex(), parent.age)
self.animalIcon:setImageFilename(visual.store.imageFilename)
local baseStatsTexts = {}
local advancedStatsTexts = {}
local horseStatsTexts = {}
local geneticsStatsTexts = {}
local text = {
title = g_i18n:getText("rl_ui_uniqueId"),
text = uniqueId
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("rl_ui_farmId"),
text = farmId
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("infohud_age"),
text = g_i18n:formatNumMonth(parent.age)
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("infohud_health"),
text = string.format("%d %%", parent.health)
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("rl_ui_gender"),
text = g_i18n:getText("rl_ui_" .. parent.gender)
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("rl_ui_weight"),
text = string.format("%.2f", parent.weight) .. "kg"
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("rl_ui_value"),
text = g_i18n:formatMoney(parent:getSellPrice(), 2, true, true)
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("rl_ui_targetWeight"),
text = string.format("%.2f", parent.targetWeight) .. "kg"
}
table.insert(baseStatsTexts, text)
text = {
title = g_i18n:getText("rl_ui_valuePerKilo"),
text = g_i18n:formatMoney(parent:getSellPrice() / parent.weight, 2, true, true)
}
table.insert(baseStatsTexts, text)
if parent.gender == "female" then
text = {
title = g_i18n:getText("infohud_reproduction"),
text = string.format("%d %%", parent.reproduction)
}
table.insert(advancedStatsTexts, text)
local subType = parent:getSubType()
local healthFactor = parent:getHealthFactor()
local canReproduce = g_i18n:getText("rl_ui_yes")
if parent.age < subType.reproductionMinAgeMonth then
canReproduce = g_i18n:getText("rl_ui_tooYoungBracketed")
elseif parent.isParent and parent.monthsSinceLastBirth <= 2 then
canReproduce = g_i18n:getText("rl_ui_recoveringLastBirthBracketed")
elseif not RealisticLivestock.hasMaleAnimalInPen(parent.clusterSystem.owner.spec_husbandryAnimals, subType.name) and not parent.isPregnant then
canReproduce = g_i18n:getText("rl_ui_noMaleAnimalBracketed")
elseif healthFactor < subType.reproductionMinHealth then
canReproduce = g_i18n:getText("rl_ui_unhealthyBracketed")
end
text = {
title = g_i18n:getText("rl_ui_canReproduce"),
text = canReproduce
}
table.insert(advancedStatsTexts, text)
if parent.age >= subType.reproductionMinAgeMonth then
text = {
title = g_i18n:getText("rl_ui_pregnant"),
text = parent.isPregnant and g_i18n:getText("rl_ui_yes") or g_i18n:getText("rl_ui_no")
}
table.insert(advancedStatsTexts, text)
end
if parent.isPregnant then
text = {
title = g_i18n:getText("rl_ui_impregnatedBy"),
text = (parent.impregnatedBy ~= nil and parent.impregnatedBy.uniqueId ~= "-1") and parent.impregnatedBy.uniqueId or g_i18n:getText("rl_ui_unknown")
}
table.insert(advancedStatsTexts, text)
end
if parent.clusterSystem ~= nil and parent.clusterSystem.owner.spec_husbandryMilk ~= nil and parent.age >= 12 then
text = {
title = g_i18n:getText("rl_ui_lactating"),
text = parent.isLactating and g_i18n:getText("rl_ui_yes") or g_i18n:getText("rl_ui_no")
}
table.insert(advancedStatsTexts, text)
end
end
if string.contains(parent.subType, "HORSE", true) or string.contains(parent.subType, "STALLION", true) then
text = {
title = g_i18n:getText("infohud_riding"),
text = string.format("%d %%", parent.riding)
}
table.insert(horseStatsTexts, text)
text = {
title = g_i18n:getText("infohud_fitness"),
text = string.format("%d %%", parent.fitness)
}
table.insert(horseStatsTexts, text)
text = {
title = g_i18n:getText("statistic_cleanliness"),
text = string.format("%d %%", parent.dirt)
}
table.insert(horseStatsTexts, text)
end
geneticsStatsTexts = parent:addGeneticsInfo()
self:setTexts(baseStatsTexts, advancedStatsTexts, horseStatsTexts, geneticsStatsTexts)
return true
end
================================================
FILE: src/gui/DiseaseDialog.lua
================================================
DiseaseDialog = {}
local diseaseDialog_mt = Class(DiseaseDialog, MessageDialog)
local modDirectory = g_currentModDirectory
function DiseaseDialog.register()
local dialog = DiseaseDialog.new()
g_gui:loadGui(modDirectory .. "gui/DiseaseDialog.xml", "DiseaseDialog", dialog)
DiseaseDialog.INSTANCE = dialog
end
function DiseaseDialog.new(target, customMt)
local self = MessageDialog.new(target, customMt or diseaseDialog_mt)
return self
end
function DiseaseDialog.createFromExistingGui(gui)
DiseaseDialog.register()
DiseaseDialog.show()
end
function DiseaseDialog.show(animal)
if DiseaseDialog.INSTANCE == nil then DiseaseDialog.register() end
local dialog = DiseaseDialog.INSTANCE
dialog.animal = animal
dialog.diseases = table.clone(animal.diseases)
g_gui:showDialog("DiseaseDialog")
end
function DiseaseDialog:onOpen()
DiseaseDialog:superClass().onOpen(self)
self.diseaseList:reloadData()
self:onClickListItem(1)
end
function DiseaseDialog:onClickOk()
local disease = self.diseases[self.diseaseList.selectedIndex]
if disease == nil or disease.type.treatment == nil or disease.cured then return end
disease.beingTreated = not disease.beingTreated
if not disease.beingTreated then
self.animal:addMessage("DISEASE_TREATMENT_STOP", { disease.type.name })
else
self.animal:addMessage("DISEASE_TREATMENT_" .. (disease.treatmentDuration > 0 and "RESUME" or "START"), { disease.type.name, string.format(g_i18n:getText("rl_ui_feePerMonth"), g_i18n:formatMoney(disease.type.treatment.cost, 2, true, true)) })
end
for _, aDisease in pairs(self.animal.diseases) do
if aDisease.type.title == disease.type.title then
aDisease.beingTreated = disease.beingTreated
break
end
end
self:onClickListItem(self.diseaseList.selectedIndex)
self.diseaseList:reloadData()
end
function DiseaseDialog:onClickListItem(index)
local disease = self.diseases[index]
if disease == nil or disease.type.treatment == nil or disease.cured then
self.yesButton:setDisabled(true)
return
end
self.yesButton:setDisabled(false)
self.yesButton:setText(g_i18n:getText("rl_ui_" .. (disease.beingTreated and "stop" or (disease.treatmentDuration > 0 and "resume" or "start")) .. "Treatment"))
end
function DiseaseDialog:getNumberOfSections()
return 1
end
function DiseaseDialog:getNumberOfItemsInSection(list, section)
return #self.animal.diseases
end
function DiseaseDialog:getTitleForSectionHeader(list, section)
return ""
end
function DiseaseDialog:populateCellForItemInSection(list, section, index, cell)
local disease = self.diseases[index]
if disease == nil then return end
local type = disease.type
local treatment = type.treatment
cell:getAttribute("title"):setText(type.name)
cell:getAttribute("duration"):setText(treatment == nil and "N/A" or RealisticLivestock.formatAge(treatment.duration - disease.treatmentDuration))
cell:getAttribute("fee"):setText(treatment == nil and "N/A" or string.format(g_i18n:getText("rl_ui_feePerMonth"), g_i18n:formatMoney(treatment.cost, 2, true, true)))
cell:getAttribute("status"):setText(disease:getStatus())
cell.setSelected = Utils.appendedFunction(cell.setSelected, function(cell, selected)
if selected then self:onClickListItem(index) end
end)
end
================================================
FILE: src/gui/EarTagColourPickerDialog.lua
================================================
EarTagColourPickerDialog = {}
local earTagColourPickerDialog_mt = Class(EarTagColourPickerDialog, MessageDialog)
local modDirectory = g_currentModDirectory
EarTagColourPickerDialog.INPUT_THRESHOLD = 0.01
EarTagColourPickerDialog.INPUT_SCALE = 10000
EarTagColourPickerDialog.DOUBLE_CLICK_INTERVAL = 400
function EarTagColourPickerDialog.register()
local dialog = EarTagColourPickerDialog.new()
g_gui:loadGui(modDirectory .. "gui/EarTagColourPickerDialog.xml", "EarTagColourPickerDialog", dialog)
EarTagColourPickerDialog.INSTANCE = dialog
end
function EarTagColourPickerDialog.show()
if EarTagColourPickerDialog.INSTANCE ~= nil then
local dialog = EarTagColourPickerDialog.INSTANCE
g_gui:showDialog("EarTagColourPickerDialog")
end
end
function EarTagColourPickerDialog.new(target, customMt)
local self = MessageDialog.new(target, customMt or earTagColourPickerDialog_mt)
self.animalTypes = g_currentMission.animalSystem:getTypes()
self.texts = {
["earTagLeft"] = {},
["earTagRight"] = {}
}
return self
end
function EarTagColourPickerDialog.createFromExistingGui(gui, _)
EarTagColourPickerDialog.register()
EarTagColourPickerDialog.show()
end
function EarTagColourPickerDialog:onGuiSetupFinished()
EarTagColourPickerDialog:superClass().onGuiSetupFinished(self)
local typeTexts = {}
for _, type in pairs(self.animalTypes) do
table.insert(typeTexts, type.name)
end
self.animalTypePicker:setTexts(typeTexts)
local rgbTexts = {}
local hsvTexts = {}
for i = 0, 360 do
table.insert(hsvTexts, tostring(i))
if i <= 255 then table.insert(rgbTexts, tostring(i)) end
end
self.hueSliderBase:setTexts(hsvTexts)
self.hueSliderText:setTexts(hsvTexts)
self.baseRgbRed:setTexts(rgbTexts)
self.baseRgbGreen:setTexts(rgbTexts)
self.baseRgbBlue:setTexts(rgbTexts)
self.textRgbRed:setTexts(rgbTexts)
self.textRgbGreen:setTexts(rgbTexts)
self.textRgbBlue:setTexts(rgbTexts)
end
function EarTagColourPickerDialog:onOpen()
EarTagColourPickerDialog:superClass().onOpen(self)
self.customPickerUpDownEventId = g_inputBinding:registerActionEvent(InputAction.AXIS_PICK_COLOR_UPDOWN, self, self.onVerticalCursorInput, false, false, true, true)
self.customPickerLeftRightEventId = g_inputBinding:registerActionEvent(InputAction.AXIS_PICK_COLOR_LEFTRIGHT, self, self.onHorizontalCursorInput, false, false, true, true)
self.accumHorizontalInput, self.accumVerticalInput = 0, 0
self.colorRender:createScene()
self.animalTypePicker:setState(1)
self.context = "earTagLeft"
self:setColourFromType(1)
end
function EarTagColourPickerDialog:onClose()
EarTagColourPickerDialog:superClass().onClose(self)
g_inputBinding:removeActionEventsByTarget(self)
self.colorRender:destroyScene()
self.renderNode = nil
end
function EarTagColourPickerDialog:onAnimalTypeChanged()
local index = self.animalTypePicker:getState()
self:setColourFromType(index)
end
function EarTagColourPickerDialog:setColourFromType(index)
local baseColour = self.animalTypes[index].colours[self.context]
local textColour = self.animalTypes[index].colours[self.context .. "_text"]
self.baseRgbRed:setState(math.floor(baseColour[1] * 255) + 1)
self.baseRgbGreen:setState(math.floor(baseColour[2] * 255) + 1)
self.baseRgbBlue:setState(math.floor(baseColour[3] * 255) + 1)
self.textRgbRed:setState(math.floor(textColour[1] * 255) + 1)
self.textRgbGreen:setState(math.floor(textColour[2] * 255) + 1)
self.textRgbBlue:setState(math.floor(textColour[3] * 255) + 1)
self:onBaseRGBChanged()
self:onTextRGBChanged()
end
function EarTagColourPickerDialog:onBaseRGBChanged()
local baseR = (self.baseRgbRed:getState() - 1) / 255
local baseG = (self.baseRgbGreen:getState() - 1) / 255
local baseB = (self.baseRgbBlue:getState() - 1) / 255
local hsvR, hsvG, hsvB = GuiUtils.rgbToHSV(baseR, baseG, baseB)
local r, g, b = GuiUtils.hsvToRGB(hsvR)
self.customPickerBase:setImageColor(nil, math.pow(r, 2.2), math.pow(g, 2.2), math.pow(b, 2.2), 1)
self.hueSliderBase:setState(math.floor(hsvR * 360) + 1)
self.baseCursor:setAbsolutePosition(self.customPickerBase.absPosition[1] + self.customPickerBase.absSize[1] * hsvG - self.baseCursor.absSize[1] * 0.5, self.customPickerBase.absPosition[2] + self.customPickerBase.absSize[2] * hsvB - self.baseCursor.absSize[2] * 0.5)
self:setCustomColorHSVBase(math.floor(hsvR * 360), hsvG, hsvB)
self.pendingRenderUpdate = true
end
function EarTagColourPickerDialog:onTextRGBChanged()
local textR = (self.textRgbRed:getState() - 1) / 255
local textG = (self.textRgbGreen:getState() - 1) / 255
local textB = (self.textRgbBlue:getState() - 1) / 255
local hsvR, hsvG, hsvB = GuiUtils.rgbToHSV(textR, textG, textB)
local r, g, b = GuiUtils.hsvToRGB(hsvR)
self.customPickerText:setImageColor(nil, math.pow(r, 2.2), math.pow(g, 2.2), math.pow(b, 2.2), 1)
self.hueSliderText:setState(math.floor(hsvR * 360) + 1)
self.textCursor:setAbsolutePosition(self.customPickerText.absPosition[1] + self.customPickerText.absSize[1] * hsvG - self.textCursor.absSize[1] * 0.5, self.customPickerText.absPosition[2] + self.customPickerText.absSize[2] * hsvB - self.textCursor.absSize[2] * 0.5)
self:setCustomColorHSVText(math.floor(hsvR * 360), hsvG, hsvB)
self.pendingRenderUpdate = true
end
function EarTagColourPickerDialog:onBaseHueChanged(index)
local hsvR, hsvG, hsvB = GuiUtils.hsvToRGB((index - 1) / 360)
local r = math.pow(hsvR, 2.2)
local g = math.pow(hsvG, 2.2)
local b = math.pow(hsvB, 2.2)
self.customPickerBase:setImageColor(nil, r, g, b, 1)
self.baseRgbRed:setState(math.floor(r * 255) + 1)
self.baseRgbGreen:setState(math.floor(g * 255) + 1)
self.baseRgbBlue:setState(math.floor(b * 255) + 1)
self:setCustomColorHSVBase(index - 1)
self.pendingRenderUpdate = true
end
function EarTagColourPickerDialog:onTextHueChanged(index)
local hsvR, hsvG, hsvB = GuiUtils.hsvToRGB((index - 1) / 360)
local r = math.pow(hsvR, 2.2)
local g = math.pow(hsvG, 2.2)
local b = math.pow(hsvB, 2.2)
self.customPickerText:setImageColor(nil, r, g, b, 1)
self.textRgbRed:setState(math.floor(r * 255) + 1)
self.textRgbGreen:setState(math.floor(g * 255) + 1)
self.textRgbBlue:setState(math.floor(b * 255) + 1)
self:setCustomColorHSVText(index - 1)
self.pendingRenderUpdate = true
end
function EarTagColourPickerDialog:setCustomColorHSVBase(index, hsvG, hsvB)
if hsvG ~= nil and hsvB ~= nil then
self.baseCursor:setAbsolutePosition(self.customPickerBase.absPosition[1] + self.customPickerBase.absSize[1] * hsvG - self.baseCursor.absSize[1] * 0.5, self.customPickerBase.absPosition[2] + self.customPickerBase.absSize[2] * hsvB - self.baseCursor.absSize[2] * 0.5)
end
if not hsvG then
local sG = (self.baseCursor.absPosition[1] + self.baseCursor.absSize[1] * 0.5 - self.customPickerBase.absPosition[1]) / self.customPickerBase.absSize[1]
hsvG = math.clamp(sG, 0, 1)
end
if not hsvB then
local sB = (self.baseCursor.absPosition[2] + self.baseCursor.absSize[2] * 0.5 - self.customPickerBase.absPosition[2]) / self.customPickerBase.absSize[2]
hsvB = math.clamp(sB, 0, 1)
end
local r, g, b = GuiUtils.hsvToRGB(index / 360, hsvG, hsvB)
self.baseRgbRed:setState(math.floor(r * 255) + 1)
self.baseRgbGreen:setState(math.floor(g * 255) + 1)
self.baseRgbBlue:setState(math.floor(b * 255) + 1)
self.pendingRenderUpdate = true
end
function EarTagColourPickerDialog:setCustomColorHSVText(index, hsvG, hsvB)
if hsvG ~= nil and hsvB ~= nil then
self.textCursor:setAbsolutePosition(self.customPickerText.absPosition[1] + self.customPickerText.absSize[1] * hsvG - self.textCursor.absSize[1] * 0.5, self.customPickerText.absPosition[2] + self.customPickerText.absSize[2] * hsvB - self.textCursor.absSize[2] * 0.5)
end
if not hsvG then
local sG = (self.textCursor.absPosition[1] + self.textCursor.absSize[1] * 0.5 - self.customPickerText.absPosition[1]) / self.customPickerText.absSize[1]
hsvG = math.clamp(sG, 0, 1)
end
if not hsvB then
local sB = (self.textCursor.absPosition[2] + self.textCursor.absSize[2] * 0.5 - self.customPickerText.absPosition[2]) / self.customPickerText.absSize[2]
hsvB = math.clamp(sB, 0, 1)
end
local r, g, b = GuiUtils.hsvToRGB(index / 360, hsvG, hsvB)
self.textRgbRed:setState(math.floor(r * 255) + 1)
self.textRgbGreen:setState(math.floor(g * 255) + 1)
self.textRgbBlue:setState(math.floor(b * 255) + 1)
self.pendingRenderUpdate = true
end
function EarTagColourPickerDialog:onClickOk()
local typeIndex = self.animalTypePicker:getState()
local baseR = (self.baseRgbRed:getState() - 1) / 255
local baseG = (self.baseRgbGreen:getState() - 1) / 255
local baseB = (self.baseRgbBlue:getState() - 1) / 255
local textR = (self.textRgbRed:getState() - 1) / 255
local textG = (self.textRgbGreen:getState() - 1) / 255
local textB = (self.textRgbBlue:getState() - 1) / 255
local type = g_currentMission.animalSystem.types[typeIndex]
type.colours[self.context] = { baseR, baseG, baseB }
type.colours[self.context .. "_text"] = { textR, textG, textB }
local leftTag, leftText, rightTag, rightText
if self.context == "earTagLeft" then leftTag, leftText = { baseR, baseG, baseB }, { textR, textG, textB } end
if self.context == "earTagRight" then rightTag, rightText = { baseR, baseG, baseB }, { textR, textG, textB } end
for _, placeable in pairs(g_currentMission.husbandrySystem.placeables) do
if placeable:getAnimalTypeIndex() ~= typeIndex then continue end
local animals = placeable:getClusters()
for _, animal in pairs(animals) do animal:setVisualEarTagColours(leftTag, leftText, rightTag, rightText) end
end
end
function EarTagColourPickerDialog:onClickEarTagLeft()
self.context = "earTagLeft"
self.animalTypePicker:setState(1)
self:setColourFromType(1)
for _, node in pairs(self.texts.earTagLeft) do setVisibility(node, true) end
for _, node in pairs(self.texts.earTagRight) do setVisibility(node, false) end
end
function EarTagColourPickerDialog:onClickEarTagRight()
self.context = "earTagRight"
self.animalTypePicker:setState(1)
self:setColourFromType(1)
for _, node in pairs(self.texts.earTagLeft) do setVisibility(node, false) end
for _, node in pairs(self.texts.earTagRight) do setVisibility(node, true) end
end
function EarTagColourPickerDialog:update(dT)
EarTagColourPickerDialog:superClass().update(self, dT)
if self.pendingRenderUpdate and self.colorRender.scene ~= nil then
local baseR = (self.baseRgbRed:getState() - 1) / 255
local baseG = (self.baseRgbGreen:getState() - 1) / 255
local baseB = (self.baseRgbBlue:getState() - 1) / 255
local textR = (self.textRgbRed:getState() - 1) / 255
local textG = (self.textRgbGreen:getState() - 1) / 255
local textB = (self.textRgbBlue:getState() - 1) / 255
if self.renderNode == nil then self:setupScene() end
setShaderParameter(self.renderNode, "colorScale", baseR, baseG, baseB)
for _, node in pairs(self.texts[self.context]) do change3DLinkedTextColour(node, textR, textG, textB, 1) end
self.colorRender:setRenderDirty()
self.pendingRenderUpdate = false
end
end
function EarTagColourPickerDialog:setupScene()
self.renderNode = getChildAt(getChildAt(self.colorRender.scene, 0), 0)
local node = getChild(self.renderNode, "front")
local uniqueId, farmId, countryCode, name, birthday = "405070", "109824", "UK", "Little Megan", "05/11/22"
set3DTextAutoScale(true)
set3DTextRemoveSpaces(true)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_MIDDLE)
setTextAlignment(RenderText.ALIGN_CENTER)
setTextColor(0, 0, 0, 1)
setTextFont(RealisticLivestock.FONTS.dejavu_sans)
self.texts.earTagLeft = {
["uniqueId"] = create3DLinkedText(node, 0, -0.006, -0.015, 0, 0, 0, 0.035, uniqueId),
["farmId"] = create3DLinkedText(node, 0, -0.041, -0.02, 0, 0, 0, 0.05, farmId),
["country"] = create3DLinkedText(node, 0, 0.021, -0.015, 0, 0, 0, 0.03, countryCode)
}
self.texts.earTagRight = {
["birthday"] = create3DLinkedText(node, 0, 0.018, -0.015, 0, 0, 0, 0.02, birthday)
}
setTextFont(RealisticLivestock.FONTS.toms_handwritten)
set3DTextWrapWidth(0.14)
set3DTextWordsPerLine(1)
setTextLineHeightScale(0.75)
self.texts.earTagRight.name = create3DLinkedText(node, 0, -0.01, -0.015, 0, 0, 0, 0.035, name)
setTextLineHeightScale(1.1)
set3DTextWordsPerLine(0)
set3DTextAutoScale(false)
set3DTextRemoveSpaces(false)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_BASELINE)
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(1, 1, 1, 1)
set3DTextWrapWidth(0)
setTextFont()
setVisibility(self.texts.earTagRight.birthday, false)
setVisibility(self.texts.earTagRight.name, false)
end
function EarTagColourPickerDialog:mouseEvent(pixelX, pixelY, isDown, isUp)
local pixelsX = 20 * g_pixelSizeX
local pixelsY = 20 * g_pixelSizeY
if GuiUtils.checkOverlayOverlap(pixelX, pixelY, self.customPickerBase.absPosition[1] - pixelsX, self.customPickerBase.absPosition[2] - pixelsY, self.customPickerBase.absSize[1] + pixelsX * 2, self.customPickerBase.absSize[2] + pixelsY * 2) and self.customPickerBase:getIsVisible() then
if isDown then
self.inputDown = true
end
if self.inputDown then
local positionX = math.clamp(pixelX, self.customPickerBase.absPosition[1], self.customPickerBase.absPosition[1] + self.customPickerBase.absSize[1])
local positionY = math.clamp(pixelY, self.customPickerBase.absPosition[2], self.customPickerBase.absPosition[2] + self.customPickerBase.absSize[2])
self.baseCursor:setAbsolutePosition(positionX - self.baseCursor.absSize[1] * 0.5, positionY - self.baseCursor.absSize[2] * 0.5)
self:setCustomColorHSVBase(self.hueSliderBase:getState() - 1)
end
end
if GuiUtils.checkOverlayOverlap(pixelX, pixelY, self.customPickerText.absPosition[1] - pixelsX, self.customPickerText.absPosition[2] - pixelsY, self.customPickerText.absSize[1] + pixelsX * 2, self.customPickerText.absSize[2] + pixelsY * 2) and self.customPickerText:getIsVisible() then
if isDown then
self.inputDown = true
end
if self.inputDown then
local positionX = math.clamp(pixelX, self.customPickerText.absPosition[1], self.customPickerText.absPosition[1] + self.customPickerText.absSize[1])
local positionY = math.clamp(pixelY, self.customPickerText.absPosition[2], self.customPickerText.absPosition[2] + self.customPickerText.absSize[2])
self.textCursor:setAbsolutePosition(positionX - self.textCursor.absSize[1] * 0.5, positionY - self.textCursor.absSize[2] * 0.5)
self:setCustomColorHSVText(self.hueSliderText:getState() - 1)
end
end
if isUp then
self.inputDown = false
end
end
function EarTagColourPickerDialog:onHorizontalCursorInput(_, amount)
self.accumHorizontalInput = self.accumHorizontalInput + amount
end
function EarTagColourPickerDialog:onVerticalCursorInput(_, amount)
self.accumVerticalInput = self.accumVerticalInput + amount
end
================================================
FILE: src/gui/FileExplorerDialog.lua
================================================
FileExplorerDialog = {}
local modDirectory = g_currentModDirectory
local FileExplorerDialog_mt = Class(FileExplorerDialog, MessageDialog)
function FileExplorerDialog.register()
local dialog = FileExplorerDialog.new()
g_gui:loadGui(modDirectory .. "gui/FileExplorerDialog.xml", "FileExplorerDialog", dialog)
FileExplorerDialog.INSTANCE = dialog
end
function FileExplorerDialog.show(files, baseDirectory, callback, target)
if FileExplorerDialog.INSTANCE ~= nil then
FileExplorerDialog.INSTANCE.files = files or {}
FileExplorerDialog.INSTANCE.baseDirectory = baseDirectory or ""
FileExplorerDialog.INSTANCE.callback = callback
FileExplorerDialog.INSTANCE.target = target
g_gui:showDialog("FileExplorerDialog")
end
end
function FileExplorerDialog.new(target, customMt)
local self = MessageDialog.new(target, customMt or FileExplorerDialog_mt)
self.files = {}
self.baseDirectory = ""
self.currentFolder = {}
self.currentFolderPath = {}
self.resizeData = {
["active"] = false,
["maximise"] = false,
["delta"] = { 0, 0 }
}
return self
end
function FileExplorerDialog.createFromExistingGui(gui, _)
FileExplorerDialog.register()
FileExplorerDialog.show(gui.files)
end
function FileExplorerDialog:onGuiSetupFinished()
FileExplorerDialog:superClass().onGuiSetupFinished(self)
self.windowSize = {
self.dialogElement.size[1],
self.dialogElement.size[2]
}
local sum = self.windowSize[1] + self.windowSize[2]
self.resizeData.delta = { (self.windowSize[2] / sum) * 0.01, (self.windowSize[1] / sum) * 0.01 }
self.cellSize = self.windowSize[1] * 0.95
self.fileListOffset = g_currentMission.hud.gameInfoDisplay:scalePixelToScreenHeight("-30")
end
function FileExplorerDialog:onOpen()
FileExplorerDialog:superClass().onOpen(self)
self.currentFolder = self.files[1]
self.currentFolderPath = {
1
}
self.pathText:setText(self.currentFolder.path)
self.fileList:reloadData()
end
function FileExplorerDialog:onClose()
FileExplorerDialog:superClass().onClose(self)
end
function FileExplorerDialog:onClickCancel()
self:close()
end
function FileExplorerDialog:onClickOk()
self:close()
local file = self.currentFolder.files[self.fileList.selectedIndex - #self.currentFolder.folders]
if file == nil then return end
local name = file.name
local valid = file.valid
if name ~= nil and self.callback ~= nil and valid then
if self.target ~= nil then
self.callback(self.target, self.currentFolder.path .. "/" .. name)
else
self.callback(self.currentFolder.path .. "/" .. name)
end
end
end
function FileExplorerDialog:onClickResize()
self.resizeData.active = true
self.resizeData.maximise = not self.resizeData.maximise
end
function FileExplorerDialog:update(dT)
FileExplorerDialog:superClass().update(self, dT)
if self.resizeData.active then
local data = self.resizeData
self.dialogElement:setSize(self.dialogElement.size[1] + data.delta[1] * (data.maximise and 1 or -1), self.dialogElement.size[2] + data.delta[2] * (data.maximise and 1 or -1))
local size = self.dialogElement.size
if not data.maximise then
if size[1] <= self.windowSize[1] and size[2] <= self.windowSize[2] then data.active = false end
else
if size[1] >= 0.9 and size[2] >= 0.9 then data.active = false end
end
self.fileList:setSize(size[1] * 0.95, self.fileListSlider.size[2] - self.topPanel.size[2] + self.fileListOffset)
self.pathText:setSize(size[1] * 0.7275)
self.cellSize = size[1] * 0.95
for _, cell in pairs(self.fileList.elements) do cell:setSize(self.cellSize) end
self.fileList:updateView(true)
end
end
function FileExplorerDialog:onClickPathUp()
self.upButton:onFocusLeave()
if #self.currentFolderPath <= 1 then return end
self.currentFolder = self.files[1]
for i = 2, #self.currentFolderPath - 1 do
self.currentFolder = self.currentFolder.folders[self.currentFolderPath[i]]
end
table.remove(self.currentFolderPath, #self.currentFolderPath)
self.pathText:setText(self.currentFolder.path)
self.fileList:reloadData()
end
function FileExplorerDialog:getNumberOfSections()
if self.currentFolder == nil or (#self.currentFolder.folders == 0 and #self.currentFolder.files == 0) then return 0 end
return 1
end
function FileExplorerDialog:getNumberOfItemsInSection(list, section)
return self.currentFolder == nil and 0 or (#self.currentFolder.folders + #self.currentFolder.files)
end
function FileExplorerDialog:getTitleForSectionHeader(list, section)
return ""
end
function FileExplorerDialog:populateCellForItemInSection(list, section, index, cell)
if index <= #self.currentFolder.folders then
local folder = self.currentFolder.folders[index]
cell:getAttribute("name"):setText(folder.name)
cell:getAttribute("icon"):setImageSlice(nil, "fileTypeIcons.folder")
cell:setDisabled(g_server == nil or g_server.netIsRunning)
if g_server == nil or g_server.netIsRunning then
cell.onClickCallback = function() end
else
cell.onClickCallback = function()
self.currentFolder = folder
table.insert(self.currentFolderPath, index)
self.pathText:setText(folder.path)
self.fileList:reloadData()
end
end
else
local name = self.currentFolder.files[index - #self.currentFolder.folders].name
local extension = string.sub(name, #name - 2)
local valid = ""
if not self.currentFolder.files[index - #self.currentFolder.folders].valid then
valid = " - " .. g_i18n:getText("cl_invalidFile")
cell:setDisabled(true)
else
cell:setDisabled(false)
end
cell:getAttribute("name"):setText(name .. valid)
cell:getAttribute("icon"):setImageSlice(nil, "fileTypeIcons." .. extension)
cell.onClickCallback = function() end
end
cell:setSize(self.cellSize)
end
================================================
FILE: src/gui/InGameMenuSettingsFrame.lua
================================================
RL_InGameMenuSettingsFrame = {}
function RL_InGameMenuSettingsFrame:onFrameOpen(_)
for name, setting in pairs(RLSettings.SETTINGS) do
if setting.dependancy then
local dependancy = RLSettings.SETTINGS[setting.dependancy.name]
if dependancy ~= nil and setting.element ~= nil then setting.element:setDisabled(dependancy.state ~= setting.dependancy.state) end
end
end
end
InGameMenuSettingsFrame.onFrameOpen = Utils.appendedFunction(InGameMenuSettingsFrame.onFrameOpen, RL_InGameMenuSettingsFrame.onFrameOpen)
function RL_InGameMenuSettingsFrame:onFrameClose()
if g_server ~= nil then RLSettings.saveToXMLFile() end
RL_BroadcastSettingsEvent.sendEvent()
end
InGameMenuSettingsFrame.onFrameClose = Utils.appendedFunction(InGameMenuSettingsFrame.onFrameClose, RL_InGameMenuSettingsFrame.onFrameClose)
================================================
FILE: src/gui/MPLoadingScreen.lua
================================================
local modDirectory = g_currentModDirectory
local function getAreVersionsCompatible(version, minVersion)
local versionParts = string.split(version, ".")
local minVersionParts = string.split(minVersion, ".")
for i, versionNumber in pairs(versionParts) do
if #minVersionParts < i or versionNumber < minVersionParts[i] then return false end
if versionNumber > minVersionParts[i] then return true end
end
return true
end
function MPLoadingScreen:verifyDependencies(directory)
local xmlFile = XMLFile.load("tempModDesc", modDirectory .. "modDesc.xml")
local hasIncompatibleDependency = false
local dependencies = {}
xmlFile:iterate("modDesc.dependencies.dependency", function(_, key)
table.insert(dependencies, {
["name"] = xmlFile:getString(key),
["minVersion"] = xmlFile:getString(key .. "#version", "1.0.0.0"),
["incompatible"] = false,
["installed"] = false
})
end)
xmlFile:delete()
for _, dependency in pairs(dependencies) do
dependency.installed = g_modIsLoaded[dependency.name]
if dependency.installed then
local modXmlFile = XMLFile.load("tempDependencyModDesc", g_modNameToDirectory[dependency.name] .. "modDesc.xml")
local version = modXmlFile:getString("modDesc.version", "1.0.0.0")
modXmlFile:delete()
dependency.version = version
if not getAreVersionsCompatible(version, dependency.minVersion) then
hasIncompatibleDependency = true
dependency.incompatible = true
end
else
hasIncompatibleDependency = true
end
end
return dependencies, hasIncompatibleDependency
end
function MPLoadingScreen:dependencyProblemOnQuitOk()
doRestart(false, "")
end
MPLoadingScreen.update = Utils.overwrittenFunction(MPLoadingScreen.update, function(self, superFunc, dT)
if not self.verifiedDependencies then
local dependencies, isIncompatible = self:verifyDependencies(modDirectory)
self.verifiedDependencies = true
if isIncompatible then
local text = g_i18n:getText("rl_ui_dependencies_missing") .. "\n"
for _, dependency in pairs(dependencies) do
if not dependency.installed then
text = text .. "\n" .. string.format(g_i18n:getText("rl_ui_dependency_missing_notInstalled"), dependency.name)
elseif dependency.incompatible then
text = text .. "\n" .. string.format(g_i18n:getText("rl_ui_dependency_missing_installed"), dependency.name, dependency.version, dependency.minVersion)
end
end
OnInGameMenuMenu()
InfoDialog.show(text, self.dependencyProblemOnQuitOk, self)
return
end
end
superFunc(self, dT)
end)
================================================
FILE: src/gui/NameInputDialog.lua
================================================
NameInputDialog = {}
local nameInputDialog_mt = Class(NameInputDialog, YesNoDialog)
local function nameInputDialog_callback() end
local modDirectory = g_currentModDirectory
function NameInputDialog.register()
local dialog = NameInputDialog.new()
g_gui:loadGui(modDirectory .. "gui/NameInputDialog.xml", "NameInputDialog", dialog)
NameInputDialog.INSTANCE = dialog
dialog.textElement.maxCharacters = 20
end
function NameInputDialog.show(callback, target, text, prompt, maxCharacters, args, gender)
if NameInputDialog.INSTANCE ~= nil then
local dialog = NameInputDialog.INSTANCE
dialog:setText(text)
dialog:setCallback(callback, target, text, prompt, maxCharacters, args, gender)
g_gui:showDialog("NameInputDialog")
end
end
function NameInputDialog.new(target, customMt)
local dialog = YesNoDialog.new(target, customMt or nameInputDialog_mt)
dialog.onTextEntered = nameInputDialog_callback
dialog.callbackArgs = nil
dialog.extraInputDisableTime = 0
local dismiss = GS_IS_CONSOLE_VERSION
if dismiss then dismiss = imeIsSupported() end
dialog.doHide = dismiss
dialog.disableOpenSound = true
dialog.animalGender = "female"
return dialog
end
function NameInputDialog.createFromExistingGui(gui, _)
NameInputDialog.register()
local callback = gui.onTextEntered
local target = gui.target
local text = gui.defaultText
local prompt = gui.dialogPrompt
local maxCharacters = gui.maxCharacters
local args = gui.callbackArgs
NameInputDialog.show(callback, target, text, prompt, maxCharacters, args)
end
function NameInputDialog:onOpen()
NameInputDialog:superClass().onOpen(self)
self.extraInputDisableTime = getPlatformId() == PlatformId.SWITCH and 0 or 100
FocusManager:setFocus(self.textElement)
self.textElement.blockTime = 0
self.textElement:onFocusActivate()
self:updateButtonVisibility()
end
function NameInputDialog:onClose()
NameInputDialog:superClass().onClose(self)
if not GS_IS_CONSOLE_VERSION then self.textElement:setForcePressed(false) end
self:updateButtonVisibility()
end
function NameInputDialog:onClickRandom()
local system = g_currentMission.animalNameSystem
if system == nil then return end
local attempts = 0
local name = system:getRandomName(self.animalGender)
while attempts < 10 and (name == nil or name == "" or name == self.textElement.text) do
name = system:getRandomName(self.animalGender)
attempts = attempts + 1
end
if name == nil or name == "" then return end
self.textElement:setText(name)
end
function NameInputDialog:setText(text)
NameInputDialog:superClass().setText(self,text)
self.inputText = text
end
function NameInputDialog:setCallback(callback, target, text, prompt, maxCharacters, args, gender)
self.onTextEntered = callback or nameInputDialog_callback
self.target = target
self.callbackArgs = args
self.textElement:setText(text or "")
self.textElement.maxCharacters = maxCharacters or self.textElement.maxCharacters
if prompt ~= nil then self.dialogTextElement:setText(prompt) end
self.dialogPrompt = prompt
self.maxCharacters = maxCharacters
self.animalGender = gender
end
function NameInputDialog:sendCallback(clickOk)
local text = self.textElement.text
self:close()
local words = string.split(text, " ")
while #words > 2 do
words[2] = words[2] .. words[3]
table.remove(words, 3)
end
if #words == 2 then text = words[1] .. " " .. words[2] end
if self.target == nil then
self.onTextEntered(text, clickOk, self.callbackArgs)
else
self.onTextEntered(self.target, text, clickOk, self.callbackArgs)
end
end
function NameInputDialog:onEnterPressed( _, dismiss)
return dismiss and true or self:onClickOk()
end
function NameInputDialog:onEscPressed(_)
return self:onClickBack()
end
function NameInputDialog:onClickBack(_, _)
if self:isInputDisabled() then return true end
self:sendCallback(false)
return false
end
function NameInputDialog:onClickOk()
if self:isInputDisabled() then return true end
self:sendCallback(true)
self:updateButtonVisibility()
return false
end
function NameInputDialog:updateButtonVisibility()
if self.yesButton ~= nil then self.yesButton:setVisible(not self.textElement.imeActive) end
if self.noButton ~= nil then self.noButton:setVisible(not self.textElement.imeActive) end
end
function NameInputDialog:update(dT)
NameInputDialog:superClass().update(self, dT)
if self.reactivateNextFrame then
self.textElement.blockTime = 0
self.textElement:onFocusActivate()
self.reactivateNextFrame = false
self:updateButtonVisibility()
end
if self.extraInputDisableTime > 0 then
self.extraInputDisableTime = self.extraInputDisableTime - dT
end
end
function NameInputDialog:isInputDisabled()
local disabled
if self.extraInputDisableTime > 0 then
disabled = not self.doHide
else
disabled = false
end
return disabled
end
function NameInputDialog:disableInputForDuration(_) end
function NameInputDialog:getIsVisible()
if self.doHide then return false end
return NameInputDialog:superClass().getIsVisible(self)
end
================================================
FILE: src/gui/ProfileDialog.lua
================================================
ProfileDialog = {}
local ProfileDialog_mt = Class(ProfileDialog, MessageDialog)
local modSettingsDirectory = g_currentModSettingsDirectory
local modDirectory = g_currentModDirectory
function ProfileDialog.register()
local dialog = ProfileDialog.new()
g_gui:loadGui(modDirectory .. "gui/ProfileDialog.xml", "ProfileDialog", dialog)
ProfileDialog.INSTANCE = dialog
end
function ProfileDialog.show(context, manager, callback, target)
if ProfileDialog.INSTANCE == nil then ProfileDialog.register() end
local dialog = ProfileDialog.INSTANCE
dialog.context = context or "save"
dialog.manager = manager
dialog.callback = callback
dialog.target = target
g_gui:showDialog("ProfileDialog")
end
function ProfileDialog.new(target, customMt)
local self = MessageDialog.new(target, customMt or ProfileDialog_mt)
self.context = "save"
self.profiles = {}
self:loadProfiles()
return self
end
function ProfileDialog.createFromExistingGui(gui, _)
ProfileDialog.register()
ProfileDialog.show()
end
function ProfileDialog:loadProfiles()
local xmlFile = XMLFile.loadIfExists("aiManagerProfiles", modSettingsDirectory .. "aiManagerProfiles.xml")
if xmlFile == nil then return end
xmlFile:iterate("profiles.profile", function(_, key)
local name = xmlFile:getString(key .. "#profileName")
local manager = AIAnimalManager.new()
manager.isProfile = true
manager:loadFromXMLFile(xmlFile, key)
self.profiles[name] = manager
end)
xmlFile:delete()
end
function ProfileDialog:saveProfiles()
local xmlFile = XMLFile.create("aiManagerProfiles", modSettingsDirectory .. "aiManagerProfiles.xml", "profiles")
if xmlFile == nil then return end
local i = 0
for name, profile in pairs(self.profiles) do
local key = string.format("profiles.profile(%s)", i)
xmlFile:setString(key .. "#profileName", name)
profile:saveToXMLFile(xmlFile, key)
i = i + 1
end
xmlFile:save(false, true)
xmlFile:delete()
end
function ProfileDialog.getProfiles()
return ProfileDialog.INSTANCE.profiles
end
function ProfileDialog.getAmountOfProfiles()
local profiles = ProfileDialog.INSTANCE.profiles
if profiles == nil then return 0 end
local i = 0
for _, profile in pairs(profiles) do i = i + 1 end
return i
end
function ProfileDialog.getHasProfiles()
local profiles = ProfileDialog.INSTANCE.profiles
if profiles == nil then return false end
for name, profile in pairs(profiles) do return true end
return false
end
function ProfileDialog:onOpen()
ProfileDialog:superClass().onOpen(self)
if self.context == "save" then
self.saveContainer:setVisible(true)
self.loadContainer:setVisible(false)
self.saveButton:setVisible(true)
self.loadButton:setVisible(false)
self.buttonsPC:invalidateLayout()
FocusManager:setFocus(self.saveProfileInput)
end
if self.context == "load" then
self.saveContainer:setVisible(false)
self.loadContainer:setVisible(true)
self.saveButton:setVisible(false)
self.loadButton:setVisible(true)
self.buttonsPC:invalidateLayout()
local texts = {}
for name, profile in pairs(self.profiles) do
table.insert(texts, name)
end
self.loadProfileSelector:setTexts(texts)
self.loadProfileSelector:setState(1)
self.profileIndexToName = texts
FocusManager:setFocus(self.loadProfileSelector)
end
end
function ProfileDialog:onClickSave()
local name = self.saveProfileInput:getText()
local profile = self.manager:createProfile()
self.profiles[name] = profile
self:saveProfiles()
self:close()
self.callback(self.target)
end
function ProfileDialog:onClickLoad()
local profile = self.profiles[self.profileIndexToName[self.loadProfileSelector:getState()]]
if profile ~= nil then self.manager:copyProfile(profile) end
self:close()
self.callback(self.target)
end
================================================
FILE: src/gui/RL_InfoDisplayKeyValueBox.lua
================================================
RL_InfoDisplayKeyValueBox = {}
local rl_InfoDisplayKeyValueBox_mt = Class(RL_InfoDisplayKeyValueBox, InfoDisplayBox)
function RL_InfoDisplayKeyValueBox.new(infoDisplay, uiScale)
local self = InfoDisplayBox.new(infoDisplay, uiScale, rl_InfoDisplayKeyValueBox_mt)
self.lines = {}
self.title = "Unknown Title"
local r, g, b, a = unpack(HUD.COLOR.BACKGROUND)
self.bgScale = g_overlayManager:createOverlay("gui.fieldInfo_middle", 0, 0, 0, 0)
self.bgScale:setColor(r, g, b, a)
self.bgBottom = g_overlayManager:createOverlay("gui.fieldInfo_bottom", 0, 0, 0, 0)
self.bgBottom:setColor(r, g, b, a)
self.bgTop = g_overlayManager:createOverlay("gui.fieldInfo_top", 0, 0, 0, 0)
self.bgTop:setColor(r, g, b, a)
r, g, b, a = unpack(HUD.COLOR.ACTIVE)
self.warningIcon = g_overlayManager:createOverlay("gui.fieldInfo_warning", 0, 0, 0, 0)
self.warningIcon:setColor(r, g, b, a)
return self
end
function RL_InfoDisplayKeyValueBox:delete()
self.bgScale:delete()
self.bgBottom:delete()
self.bgTop:delete()
self.warningIcon:delete()
end
function RL_InfoDisplayKeyValueBox:storeScaledValues()
local infoDisplay = self.infoDisplay
local x, z = infoDisplay:scalePixelValuesToScreenVector(340, 6)
local y = infoDisplay:scalePixelToScreenHeight(6)
self.bgBottom:setDimension(x, z)
self.bgTop:setDimension(x, y)
self.bgScale:setDimension(x, 0)
self.boxWidth = infoDisplay:scalePixelToScreenWidth(340)
self.keyTextSize = infoDisplay:scalePixelToScreenHeight(14)
self.valueTextSize = infoDisplay:scalePixelToScreenHeight(14)
self.titleTextSize = infoDisplay:scalePixelToScreenHeight(15)
self.titleToLineOffsetY = infoDisplay:scalePixelToScreenHeight(-24)
self.lineToLineOffsetY = infoDisplay:scalePixelToScreenHeight(-21)
self.lineHeight = infoDisplay:scalePixelToScreenHeight(21)
self.titleAndBoxHeight = infoDisplay:scalePixelToScreenHeight(45)
self.dashedLineHeight = g_pixelSizeY
self.dashWidth = infoDisplay:scalePixelToScreenWidth(6)
self.dashGapWidth = infoDisplay:scalePixelToScreenWidth(3)
self.keyOffsetX = infoDisplay:scalePixelToScreenWidth(30)
local a, b = infoDisplay:scalePixelValuesToScreenVector(30, -3)
self.warningOffsetX = a
self.warningOffsetY = b
self.valueOffsetX = infoDisplay:scalePixelToScreenWidth(-14)
local c, d = infoDisplay:scalePixelValuesToScreenVector(14, -27)
self.titleOffsetX = c
self.titleOffsetY = d
self.titleMaxWidth = infoDisplay:scalePixelToScreenWidth(312)
local e, f = infoDisplay:scalePixelValuesToScreenVector(20, 20)
self.warningIcon:setDimension(e, f)
local g, h = infoDisplay:scalePixelValuesToScreenVector(10, -4)
self.warningIconOffsetX = g
self.warningIconOffsetY = h
end
function RL_InfoDisplayKeyValueBox:draw(posX, posY)
local leftX = posX - self.boxWidth
local height = self.titleAndBoxHeight
for _, line in ipairs(self.lines) do
if line.isActive then
height = height + self.lineHeight
if line.isWarning then height = height + math.abs(self.warningOffsetY) end
end
end
self.bgScale:setDimension(nil, height - self.bgBottom.height - self.bgTop.height)
self.bgBottom:setPosition(leftX, posY)
self.bgBottom:render()
self.bgScale:setPosition(leftX, self.bgBottom.y + self.bgBottom.height)
self.bgScale:render()
self.bgTop:setPosition(leftX, self.bgScale.y + self.bgScale.height)
self.bgTop:render()
local a = leftX + self.titleOffsetX
local b = self.bgTop.y + self.bgTop.height + self.titleOffsetY
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(1, 1, 1, 1)
setTextBold(true)
renderText(a, b, self.titleTextSize, self.title)
setTextBold(false)
local c = leftX + self.keyOffsetX
local d = leftX + self.warningOffsetX
local e = leftX + self.warningIconOffsetX
local f = posX + self.valueOffsetX
local g = b + self.titleToLineOffsetY
local h = HUD.COLOR.ACTIVE
local i = HUD.COLOR.INACTIVE
for _, line in ipairs(self.lines) do
if line.isActive then
local key = line.key
local value = line.value
if line.isWarning then
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(h[1], h[2], h[3], h[4])
setTextBold(true)
g = g + self.warningOffsetY
renderText(d, g, self.keyTextSize, key)
setTextBold(false)
self.warningIcon:setPosition(e, g + self.warningIconOffsetY)
self.warningIcon:render()
else
if value == "rl_ui_genetics_extremelyLow" or value == "rl_ui_genetics_extremelyBad" then
setTextColor(1, 0, 0, 1)
elseif value == "rl_ui_genetics_veryLow" or value == "rl_ui_genetics_veryBad" then
setTextColor(1, 0.2, 0, 1)
elseif value == "rl_ui_genetics_low" or value == "rl_ui_genetics_bad" then
setTextColor(1, 0.52, 0, 1)
elseif value == "rl_ui_genetics_average" then
setTextColor(1, 1, 0, 1)
elseif value == "rl_ui_genetics_high" or value == "rl_ui_genetics_good" then
setTextColor(0.52, 1, 0, 1)
elseif value == "rl_ui_genetics_veryHigh" or value == "rl_ui_genetics_veryGood" then
setTextColor(0.2, 1, 0, 1)
else
setTextColor(0, 1, 0, 1)
end
value = g_i18n:getText(value)
if key == "rl_ui_overall" then
setTextBold(true)
key = g_i18n:getText(key)
end
setTextAlignment(RenderText.ALIGN_LEFT)
renderText(c, g, self.keyTextSize, key)
local j = getTextWidth(self.keyTextSize, key)
setTextAlignment(RenderText.ALIGN_RIGHT)
renderText(f, g, self.valueTextSize, value)
local k = getTextWidth(self.valueTextSize, value)
local l = c + j + 3 * g_pixelSizeX
local m = f - k - l - 3 * g_pixelSizeX
drawDashedLine(l, g, m, self.dashedLineHeight, self.dashWidth, self.dashGapWidth, i[1], i[2], i[3], i[4], true)
setTextBold(false)
end
g = g + self.lineToLineOffsetY
end
end
local newPosY = self.bgTop.y + self.bgTop.height
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(1, 1, 1, 1)
self.doShowNextFrame = false
return posX, newPosY
end
function RL_InfoDisplayKeyValueBox:canDraw()
return self.doShowNextFrame
end
function RL_InfoDisplayKeyValueBox:showNextFrame()
self.doShowNextFrame = true
end
function RL_InfoDisplayKeyValueBox:clear()
for _, lines in ipairs(self.lines) do
lines.isActive = false
end
self.currentLineIndex = 0
end
function RL_InfoDisplayKeyValueBox:addLine(key, value, accentuate)
self.currentLineIndex = self.currentLineIndex + 1
local line = self.lines[self.currentLineIndex]
if line == nil then
line = {
["key"] = "",
["value"] = "",
["isWarning"] = false
}
table.addElement(self.lines, line)
end
line.key = key
line.value = value or ""
line.isWarning = accentuate
line.isActive = true
end
function RL_InfoDisplayKeyValueBox:setTitle(title)
local newTitle = utf8ToUpper(title)
if newTitle ~= self.title then
self.title = Utils.limitTextToWidth(newTitle, self.titleTextSize, self.titleMaxWidth, false, "...")
end
end
================================================
FILE: src/gui/RealisticLivestockFrame.lua
================================================
RealisticLivestockFrame = {}
local realisticLivestockFrame_mt = Class(RealisticLivestockFrame, TabbedMenuFrameElement)
function RealisticLivestockFrame.new()
local self = RealisticLivestockFrame:superClass().new(nil, realisticLivestockFrame_mt)
self.name = "RealisticLivestockFrame"
self.husbandrySystem = g_currentMission.husbandrySystem
return self
end
function RealisticLivestockFrame:delete()
RealisticLivestockFrame:superClass().delete(self)
end
function RealisticLivestockFrame:initialize()
self.backButtonInfo = {
["inputAction"] = InputAction.MENU_BACK
}
self.nextPageButtonInfo = {
["inputAction"] = InputAction.MENU_PAGE_NEXT,
["text"] = g_i18n:getText("ui_ingameMenuNext"),
["callback"] = self.onPageNext
}
self.prevPageButtonInfo = {
["inputAction"] = InputAction.MENU_PAGE_PREV,
["text"] = g_i18n:getText("ui_ingameMenuPrev"),
["callback"] = self.onPagePrevious
}
self.changeMonitorsButtonInfo = {
["inputAction"] = InputAction.MENU_ACTIVATE,
["text"] = g_i18n:getText("rl_ui_applyAllMonitor"),
["callback"] = function()
self:onClickChangeMonitors()
end,
["profile"] = "buttonSelect"
}
end
function RealisticLivestockFrame:onGuiSetupFinished()
RealisticLivestockFrame:superClass().onGuiSetupFinished(self)
end
function RealisticLivestockFrame:onFrameOpen()
RealisticLivestockFrame:superClass().onFrameOpen(self)
self:updateContent()
self:resetButtonStates()
self:updateMenuButtons()
self.husbandryList:reloadData()
end
function RealisticLivestockFrame:onFrameClose()
RealisticLivestockFrame:superClass().onFrameClose(self)
end
function RealisticLivestockFrame:updateContent()
self.currentBalanceText:setText(g_i18n:formatMoney(g_currentMission:getMoney(), 2, true, true))
self.data = {}
self.selectedRow = nil
if g_localPlayer == nil then return end
local placeables = self.husbandrySystem:getPlaceablesByFarm()
for _, placeable in pairs(placeables) do
local animals = placeable:getClusters()
local numMonitored = 0
local animalTypeIndex = placeable:getAnimalTypeIndex()
local farmland = placeable:getFarmlandId()
local numAnimals = #animals
local data = {
["placeable"] = placeable,
["name"] = placeable:getName(),
["totalAnimals"] = numAnimals,
["farmland"] = farmland,
["animalTypeIndex"] = animalTypeIndex,
["fee"] = 0,
["food"] = 0,
["water"] = 0,
["straw"] = 0,
["product"] = 0,
["manure"] = 0,
["liquidManure"] = 0
}
for _, animal in pairs(animals) do
if not animal.monitor.active and not animal.monitor.removed then continue end
numMonitored = numMonitored + 1
for fillType, amount in pairs(animal.input) do
data[fillType] = data[fillType] + amount
end
for fillType, amount in pairs(animal.output) do
local target = (fillType == "pallets" or fillType == "milk") and "product" or fillType
data[target] = data[target] + amount
end
data.fee = data.fee + animal.monitor.fee
end
data.totalMonitored = numMonitored
data.percentMonitored = numAnimals == 0 and 0 or (numMonitored / numAnimals)
table.insert(self.data, data)
end
end
function RealisticLivestockFrame:updateMenuButtons()
self.menuButtonInfo = { self.backButtonInfo, self.nextPageButtonInfo, self.prevPageButtonInfo }
if self.data ~= nil and self.selectedRow ~= nil then
self.changeMonitorsButtonInfo.disabled = self.selectedRow.totalAnimals == 0
self.changeMonitorsButtonInfo.text = g_i18n:getText("rl_ui_" .. (self.selectedRow.percentMonitored == 1 and "remove" or "apply") .. "AllMonitor")
table.insert(self.menuButtonInfo, self.changeMonitorsButtonInfo)
end
self:setMenuButtonInfoDirty()
end
function RealisticLivestockFrame:resetButtonStates()
self.buttonStates = {
[self.nameButton] = { ["sorter"] = false, ["target"] = "name", ["pos"] = "-5px" },
[self.farmlandButton] = { ["sorter"] = false, ["target"] = "farmland", ["pos"] = "12px" },
[self.animalTypeButton] = { ["sorter"] = false, ["target"] = "animalTypeIndex", ["pos"] = "35px" },
[self.percentMonitoredButton] = { ["sorter"] = false, ["target"] = "percentMonitored", ["pos"] = "12px" },
[self.feeButton] = { ["sorter"] = false, ["target"] = "fee", ["pos"] = "12px" },
[self.foodButton] = { ["sorter"] = false, ["target"] = "food", ["pos"] = "22px" },
[self.waterButton] = { ["sorter"] = false, ["target"] = "water", ["pos"] = "36px" },
[self.strawButton] = { ["sorter"] = false, ["target"] = "straw", ["pos"] = "36px" },
[self.productionButton] = { ["sorter"] = false, ["target"] = "product", ["pos"] = "10px" },
[self.manureButton] = { ["sorter"] = false, ["target"] = "manure", ["pos"] = "20px" },
[self.liquidManureButton] = { ["sorter"] = false, ["target"] = "liquidManure", ["pos"] = "20px" }
}
self.sortingIcon_true:setVisible(false)
self.sortingIcon_false:setVisible(false)
end
function RealisticLivestockFrame:getNumberOfSections()
if self.data == nil or #self.data == 0 then return 0 end
return 1
end
function RealisticLivestockFrame:getNumberOfItemsInSection(list, section)
return self.data == nil and 0 or #self.data
end
function RealisticLivestockFrame:getTitleForSectionHeader(list, section)
return ""
end
function RealisticLivestockFrame:populateCellForItemInSection(list, section, index, cell)
local item = self.data[index]
cell:getAttribute("name"):setText(item.name)
cell:getAttribute("farmland"):setText(item.farmland)
local animalType
for animalName, animalIndex in pairs(AnimalType) do
if animalIndex == item.animalTypeIndex then
animalType = animalName:lower()
break
end
end
if animalType ~= nil then animalType = string.sub(animalType, 1, 1):upper() .. string.sub(animalType, 2) end
cell:getAttribute("animalType"):setText(animalType)
cell:getAttribute("percentMonitored"):setText(string.format("%s / %s", item.totalMonitored, item.totalAnimals))
cell:getAttribute("fee"):setText(string.format(g_i18n:getText("rl_ui_feePerMonth"), g_i18n:formatMoney(item.fee, 2, true, true)))
local daysPerMonth = g_currentMission.environment.daysPerPeriod
cell:getAttribute("food"):setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (item.food * 24) / daysPerMonth))
cell:getAttribute("water"):setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (item.water * 24) / daysPerMonth))
cell:getAttribute("straw"):setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (item.straw * 24) / daysPerMonth))
cell:getAttribute("product"):setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (item.product * 24) / daysPerMonth))
cell:getAttribute("manure"):setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (item.manure * 24) / daysPerMonth))
cell:getAttribute("liquidManure"):setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (item.liquidManure * 24) / daysPerMonth))
cell.setSelected = Utils.appendedFunction(cell.setSelected, function(cell, selected)
if selected then self:onClickListItem(cell) end
end)
end
function RealisticLivestockFrame:onClickSortButton(button)
local buttonState = self.buttonStates[button]
self["sortingIcon_" .. tostring(buttonState.sorter)]:setVisible(false)
self["sortingIcon_" .. tostring(not buttonState.sorter)]:setVisible(true)
self["sortingIcon_" .. tostring(not buttonState.sorter)]:setPosition(button.position[1] + GuiUtils.getNormalizedXValue(buttonState.pos), 0)
buttonState.sorter = not buttonState.sorter
local sorter = buttonState.sorter
local target = buttonState.target
table.sort(self.data, function(a, b)
if sorter then return a[target] > b[target] end
return a[target] < b[target]
end)
self.husbandryList:reloadData()
end
function RealisticLivestockFrame:onClickListItem(item)
self.selectedRow = nil
local index = item.indexInSection
if self.data == nil or self.data[index] == nil then
self:updateMenuButtons()
return
end
self.selectedRow = self.data[index]
self:updateMenuButtons()
end
function RealisticLivestockFrame:onClickChangeMonitors()
local selectedRow = self.selectedRow
if selectedRow == nil then return end
local animals = selectedRow.placeable:getClusters()
if selectedRow.percentMonitored == 1 then
for _, animal in pairs(animals) do
animal.monitor.active = false
animal.monitor.removed = true
AnimalMonitorEvent.sendEvent(selectedRow.placeable, animal, false, true)
end
else
for _, animal in pairs(animals) do
animal.monitor.active = true
animal.monitor.removed = false
AnimalMonitorEvent.sendEvent(selectedRow.placeable, animal, true, false)
end
end
selectedRow.food, selectedRow.water, selectedRow.straw, selectedRow.product, selectedRow.manure, selectedRow.liquidManure, selectedRow.fee, selectedRow.totalMonitored = 0, 0, 0, 0, 0, 0, 0, 0
for _, animal in pairs(animals) do
if not animal.monitor.active and not animal.monitor.removed then continue end
selectedRow.totalMonitored = selectedRow.totalMonitored + 1
for fillType, amount in pairs(animal.input) do
selectedRow[fillType] = selectedRow[fillType] + amount
end
for fillType, amount in pairs(animal.output) do
local target = (fillType == "pallets" or fillType == "milk") and "product" or fillType
selectedRow[target] = selectedRow[target] + amount
end
selectedRow.fee = selectedRow.fee + animal.monitor.fee
end
selectedRow.percentMonitored = selectedRow.totalAnimals == 0 and 0 or (selectedRow.totalMonitored / selectedRow.totalAnimals)
self.husbandryList:reloadData()
self:updateMenuButtons()
end
================================================
FILE: src/gui/RealisticLivestock_AnimalScreen.lua
================================================
RealisticLivestock_AnimalScreen = {}
AnimalScreen.DEWAR_QUANTITIES = {
1,
2,
3,
4,
5,
10,
15,
20,
25,
30,
40,
50,
75,
100,
150,
200,
250,
300,
400,
500,
750,
1000
}
function RealisticLivestock_AnimalScreen.show(husbandry, vehicle, isDealer)
--if husbandry == nil and vehicle == nil then return end
g_animalScreen.isTrailerFarm = husbandry ~= nil and vehicle ~= nil
g_animalScreen.filters = nil
g_animalScreen.filteredItems = nil
g_animalScreen:setController(husbandry, vehicle, isDealer)
g_gui:showGui("AnimalScreen")
end
AnimalScreen.show = RealisticLivestock_AnimalScreen.show
function RealisticLivestock_AnimalScreen:setController(_, husbandry, vehicle, isDealer)
--if husbandry ~= nil then self.tabLogButton:setImageSlice(nil, "realistic_livestock.messages" .. (husbandry:getHasUnreadRLMessages() and "_new" or "")) end
self.isTrailer = husbandry == nil and vehicle ~= nil and not isDealer
self.isDirectFarm = husbandry ~= nil and vehicle == nil
self.isDealer = isDealer
self.husbandry = husbandry
local controller
if husbandry == nil then
if vehicle == nil then
controller = AnimalScreenDealer.new()
elseif isDealer then
controller = AnimalScreenDealerTrailer.new(vehicle)
else
controller = AnimalScreenTrailer.new(vehicle)
end
elseif vehicle == nil then
controller = AnimalScreenDealerFarm.new(husbandry)
else
controller = AnimalScreenTrailerFarm.new(husbandry, vehicle)
end
controller:init()
self.tabLog:setVisible(self.isDirectFarm)
self.tabHerdsman:setVisible(self.isDirectFarm)
self.controller = controller
self.controller:setAnimalsChangedCallback(self.onAnimalsChanged, self)
self.controller:setActionTypeCallback(self.onActionTypeChanged, self)
self.controller:setSourceActionFinishedCallback(self.onSourceActionFinished, self)
self.controller:setTargetActionFinishedCallback(self.onTargetActionFinished, self)
self.controller:setSourceBulkActionFinishedCallback(self.onSourceBulkActionFinished, self)
self.controller:setTargetBulkActionFinishedCallback(self.onTargetBulkActionFinished, self)
self.controller:setErrorCallback(self.onError, self)
self.sourceList:reloadData(true)
end
AnimalScreen.setController = Utils.overwrittenFunction(AnimalScreen.setController, RealisticLivestock_AnimalScreen.setController)
function RealisticLivestock_AnimalScreen:onGuiSetupFinished()
local function getText(key)
return g_i18n:getText(key)
end
local geneticTexts = {
getText("rl_ui_genetics_extremelyBad"),
getText("rl_ui_genetics_veryBad"),
getText("rl_ui_genetics_bad"),
getText("rl_ui_genetics_average"),
getText("rl_ui_genetics_high"),
getText("rl_ui_genetics_veryHigh"),
getText("rl_ui_genetics_extremelyHigh"),
getText("rl_ui_genetics_highest")
}
local fertilityTexts = table.clone(geneticTexts, 1)
table.insert(fertilityTexts, 1, getText("rl_ui_genetics_infertile"))
self.currentHerdsmanPage = "buy"
self.herdsmanOptions = {
["enabled"] = { ["target"] = "enabled", ["type"] = "binary", ["values"] = { false, true }, ["texts"] = { getText("setting_disasterDestructionState_disabled"), getText("setting_disasterDestructionState_enabled") } },
["budget|type"] = { ["target"] = "budget|type", ["type"] = "binary", ["values"] = { "fixed", "percentage" }, ["texts"] = { getText("rl_ui_fixed"), getText("rl_ui_percentage") } },
["budget|fixed"] = { ["target"] = "budget|fixed", ["type"] = "input", ["inputType"] = "money" },
["budget|percentage"] = { ["target"] = "budget|percentage", ["type"] = "multi", ["values"] = { 0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 6, 7, 8, 9, 10, 12.5, 15, 17.5, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100 }, ["texts"] = { "0.5%", "1%", "1.5%", "2%", "2.5%", "3%", "4%", "5%", "6%", "7%", "8%", "9%", "10%", "12.5%", "15%", "17.5%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", "60%", "70%", "80%", "90%", "100%" } },
["maxAnimals"] = { ["target"] = "maxAnimals", ["type"] = "input", ["inputType"] = "number" },
["breed"] = { ["target"] = "breed", ["type"] = "multi", ["ignoreTexts"] = true },
["semen"] = { ["target"] = "semen", ["type"] = "multi", ["ignoreTexts"] = true },
["mark"] = { ["target"] = "mark", ["type"] = "binary", ["values"] = { false, true }, ["texts"] = { getText("rl_ui_dontMark"), getText("rl_ui_mark") } },
["diseases"] = { ["target"] = "diseases", ["type"] = "binary", ["values"] = { false, true }, ["texts"] = { getText("rl_ui_noDiseases"), getText("rl_ui_any") } },
["diseasesSecondary"] = { ["target"] = "diseases", ["type"] = "binary", ["values"] = { false, true }, ["texts"] = { getText("rl_ui_any"), getText("rl_ui_onlyDiseases") } },
["gender"] = { ["target"] = "gender", ["type"] = "tripleOption", ["values"] = { "female", "any", "male" }, ["texts"] = { getText("rl_ui_female"), getText("rl_ui_any"), getText("rl_ui_male") } },
["age"] = { ["target"] = "age", ["type"] = "doubleSlider", ["ignoreTexts"] = true },
["quality"] = { ["target"] = "quality", ["type"] = "doubleSlider", ["values"] = { 25, 35, 70, 90, 110, 140, 165, 175 }, ["texts"] = geneticTexts },
["fertility"] = { ["target"] = "fertility", ["type"] = "doubleSlider", ["values"] = { 0, 25, 35, 70, 90, 110, 140, 165, 175 }, ["texts"] = fertilityTexts },
["health"] = { ["target"] = "health", ["type"] = "doubleSlider", ["values"] = { 25, 35, 70, 90, 110, 140, 165, 175 }, ["texts"] = geneticTexts },
["productivity"] = { ["target"] = "productivity", ["type"] = "doubleSlider", ["values"] = { 25, 35, 70, 90, 110, 140, 165, 175 }, ["texts"] = geneticTexts },
["metabolism"] = { ["target"] = "metabolism", ["type"] = "doubleSlider", ["values"] = { 25, 35, 70, 90, 110, 140, 165, 175 }, ["texts"] = geneticTexts },
["convention"] = { ["target"] = "convention", ["type"] = "binary", ["values"] = { "random", "alphabetical" }, ["texts"] = { getText("rl_button_random"), getText("rl_ui_alphabetical") } }
}
local function updateTooltip(element)
local option = self.herdsmanOptions[element.name]
local tooltip = element:getDescendantByName("tooltip")
tooltip:setVisible(true)
if option.type == "doubleSlider" then
local lowestState = element:getLowestState()
local highestState = element:getHighestState()
if lowestState == highestState then
tooltip:setText(string.format(getText(string.format("rl_ui_herdsmanTooltip_%s_%s_equal", self.currentHerdsmanPage, element.name)), option.texts[lowestState]))
else
tooltip:setText(string.format(getText(string.format("rl_ui_herdsmanTooltip_%s_%s_range", self.currentHerdsmanPage, element.name)), option.texts[lowestState], option.texts[highestState]))
end
elseif option.type == "input" then
if option.inputType == "money" then
tooltip:setText(string.format(getText(string.format("rl_ui_herdsmanTooltip_%s_%s", self.currentHerdsmanPage, element.name)), g_i18n:formatMoney(tonumber(element:getText()) or 0, 2, true, true)))
elseif option.inputType == "number" then
tooltip:setText(string.format(getText(string.format("rl_ui_herdsmanTooltip_%s_%s", self.currentHerdsmanPage, element.name)), g_i18n:formatNumber(tonumber(element:getText()) or 0)))
end
elseif option.type == "multi" then
if (option.target == "breed" or option.target == "semen") and option.values[element:getState()] == "any" then
tooltip:setText(getText(string.format("rl_ui_herdsmanTooltip_%s_%s_any", self.currentHerdsmanPage, option.target)))
else
tooltip:setText(string.format(getText(string.format("rl_ui_herdsmanTooltip_%s_%s", self.currentHerdsmanPage, element.name)), option.texts[element:getState()]))
end
else
tooltip:setText(getText(string.format("rl_ui_herdsmanTooltip_%s_%s_%s", self.currentHerdsmanPage, element.name, element:getState())))
end
end
self.herdsmanPages = {
["buy"] = self.herdsmanPageBuyScrollingLayout,
["sell"] = self.herdsmanPageSellScrollingLayout,
["castrate"] = self.herdsmanPageCastrateScrollingLayout,
["naming"] = self.herdsmanPageNamingScrollingLayout,
["ai"] = self.herdsmanPageAIScrollingLayout
}
for _, page in pairs(self.herdsmanPages) do
for _, element in pairs(page.elements) do
if element.name == "ignore" then continue end
element.onFocusEnter = updateTooltip
if self.herdsmanOptions[element.name].type == "input" then
element.updateVisibleTextElements = Utils.appendedFunction(element.updateVisibleTextElements, updateTooltip)
continue
end
element.updateContentElement = Utils.appendedFunction(element.updateContentElement, updateTooltip)
if self.herdsmanOptions[element.name].ignoreTexts then continue end
element:setTexts(self.herdsmanOptions[element.name].texts)
end
end
local aiQuantityTexts = {}
for _, quantity in pairs(AnimalScreen.DEWAR_QUANTITIES) do table.insert(aiQuantityTexts, string.format("%s %s", quantity, g_i18n:getText("rl_ui_straw" .. (quantity == 1 and "Single" or "Multiple")))) end
self.aiQuantitySelector:setTexts(aiQuantityTexts)
end
AnimalScreen.onGuiSetupFinished = Utils.appendedFunction(AnimalScreen.onGuiSetupFinished, RealisticLivestock_AnimalScreen.onGuiSetupFinished)
function AnimalScreen:resetMessageButtonStates()
self.messageButtonStates = {
[self.messagesImportanceButton] = { ["sorter"] = false, ["target"] = "importance", ["pos"] = "-5px" },
[self.messagesTypeButton] = { ["sorter"] = false, ["target"] = "title", ["pos"] = "50px" },
[self.messagesAnimalButton] = { ["sorter"] = false, ["target"] = "animal", ["pos"] = "30px" },
[self.messagesMessageButton] = { ["sorter"] = false, ["target"] = "text", ["pos"] = "20px" },
}
self.sortingIcon_true:setVisible(false)
self.sortingIcon_false:setVisible(false)
end
function AnimalScreen:onClickMessageSortButton(button)
local buttonState = self.messageButtonStates[button]
self["sortingIcon_" .. tostring(buttonState.sorter)]:setVisible(false)
self["sortingIcon_" .. tostring(not buttonState.sorter)]:setVisible(true)
self["sortingIcon_" .. tostring(not buttonState.sorter)]:setPosition(button.position[1] + GuiUtils.getNormalizedXValue(buttonState.pos), 0)
buttonState.sorter = not buttonState.sorter
local sorter = buttonState.sorter
local target = buttonState.target
table.sort(self.messages[self.currentMessagePage], function(a, b)
local aTarget = a[target] or RLMessage[a.id][target]
local bTarget = b[target] or RLMessage[b.id][target]
if sorter then return aTarget > bTarget end
return aTarget < bTarget
end)
self.husbandryList:reloadData()
end
function AnimalScreen:updateLog()
self:resetMessageButtonStates()
self.messageListPageNumber:setText(string.format("%s/%s", self.currentMessagePage, #self.messages))
local totalNumMessages = (#self.messages - 1) * 250 + #self.messages[#self.messages]
self.messageListMessageNumber:setText(string.format(g_i18n:getText("rl_ui_messageNumber"), (#self.messages[self.currentMessagePage] == 0 and 0 or 1) + 250 * (self.currentMessagePage - 1), (self.currentMessagePage - 1) * 250 + #self.messages[self.currentMessagePage], totalNumMessages))
self.messageListPageFirst:setDisabled(self.currentMessagePage == 1)
self.messageListPagePrevious:setDisabled(self.currentMessagePage == 1)
self.messageListPageNext:setDisabled(self.currentMessagePage == #self.messages)
self.messageListPageLast:setDisabled(self.currentMessagePage == #self.messages)
self.husbandryList:reloadData()
end
function AnimalScreen:onClickDeleteMessage()
local index = self.husbandryList.selectedIndex
local message = self.messages[self.currentMessagePage][index]
if message == nil then return end
self.husbandry:deleteRLMessage(message.uniqueId)
local currentMessagePage = self.currentMessagePage
self:onClickLogMode()
if #self.messages >= currentMessagePage then
self.currentMessagePage = currentMessagePage
self:updateLog()
end
end
function AnimalScreen:onClickMessagePageFirst()
self.currentMessagePage = 1
self:updateLog()
end
function AnimalScreen:onClickMessagePagePrevious()
self.currentMessagePage = self.currentMessagePage - 1
self:updateLog()
end
function AnimalScreen:onClickMessagePageNext()
self.currentMessagePage = self.currentMessagePage + 1
self:updateLog()
end
function AnimalScreen:onClickMessagePageLast()
self.currentMessagePage = #self.messages
self:updateLog()
end
function AnimalScreen:onClickAIMode()
self.filters = nil
self.filteredItems = nil
self.isInfoMode = false
self.isBuyMode = false
self.isLogMode = false
self.isHerdsmanMode = false
self.isAIMode = true
self.buttonBuySelected:setVisible(false)
self.buttonToggleSelectAll:setVisible(false)
self.buttonBuy:setVisible(false)
self.buttonMonitor:setVisible(false)
self.buttonArtificialInsemination:setVisible(false)
self.buttonMark:setVisible(false)
self.buttonRename:setVisible(false)
self.buttonSelect:setVisible(false)
self.buttonSell:setVisible(false)
self.buttonFilters:setVisible(false)
self.buttonDiseases:setVisible(false)
self.buttonDeleteMessage:setVisible(false)
self.buttonApplyHerdsmanSettings:setVisible(false)
self.buttonCastrate:setVisible(false)
self.buttonBuyAI:setVisible(true)
self.buttonFavourite:setVisible(true)
self.logContainer:setVisible(false)
self.herdsmanContainer:setVisible(false)
self.aiContainer:setVisible(true)
self.sourceBoxBg:setVisible(false)
self.mainContentContainer:setVisible(false)
self.tabBuy:setSelected(false)
self.tabSell:setSelected(false)
self.tabInfo:setSelected(false)
self.tabLog:setSelected(false)
self.tabHerdsman:setSelected(false)
self.tabAI:setSelected(true)
self.buttonsPanel:invalidateLayout()
self.aiAnimals = {}
local animalSystem = g_currentMission.animalSystem
local texts = {}
for animalTypeIndex, animalType in pairs(animalSystem:getTypes()) do
self.aiAnimals[animalTypeIndex] = animalSystem:getAIAnimalsByTypeIndex(animalTypeIndex)
table.sort(self.aiAnimals[animalTypeIndex], function(a, b) return a.subTypeIndex == b.subTypeIndex and a.age > b.age or a.subTypeIndex < b.subTypeIndex end)
table.insert(texts, animalType.groupTitle)
end
self.aiPageAnimalTypeSelector:setTexts(texts)
self.aiPageAnimalTypeSelector:setState(1)
self:onClickChangeAIAnimalType(1)
end
function AnimalScreen:onClickBuyAI()
if self.aiAnimals == nil or self.aiAnimalTypeIndex == nil or self.aiAnimals[self.aiAnimalTypeIndex] == nil then return end
local animal = self.aiAnimals[self.aiAnimalTypeIndex][self.aiList.selectedIndex]
if animal == nil then return end
local spawnPlaces, usedPlaces = g_currentMission.storeSpawnPlaces, g_currentMission.usedStorePlaces
local x, y, z, place, width = PlacementUtil.getPlace(spawnPlaces, { ["width"] = 1, ["height"] = 2.5, ["length"] = 1, ["widthOffset"] = 0.5, ["lengthOffset"] = 0.5 }, usedPlaces, true, true, false, true)
if x == nil then
return
end
PlacementUtil.markPlaceUsed(usedPlaces, place, width)
local farmId = g_localPlayer.farmId
local quantity = AnimalScreen.DEWAR_QUANTITIES[self.aiQuantitySelector:getState()]
local price = g_currentMission.animalSystem:getFarmSemenPrice(animal.birthday.country, animal.farmId) * quantity * Dewar.PRICE_PER_STRAW * animal.success * 2.25
for _, value in pairs(animal.genetics) do price = price * value end
local errorCode
if not g_currentMission:getHasPlayerPermission("tradeAnimals") then
errorCode = AnimalBuyEvent.BUY_ERROR_NO_PERMISSION
elseif g_currentMission:getMoney(farmId) + price < 0 then
errorCode = AnimalBuyEvent.BUY_ERROR_NOT_ENOUGH_MONEY
else
errorCode = AnimalBuyEvent.BUY_SUCCESS
g_client:getServerConnection():sendEvent(SemenBuyEvent.new(animal, quantity, -price, farmId, { x, y, z }, { 0, 0, 0 }), true)
end
self:onSemenBought(errorCode)
end
function AnimalScreen:onSemenBought(errorCode)
local dialogType = DialogElement.TYPE_INFO
local text = "rl_ui_semenPurchase_successful"
if errorCode == AnimalBuyEvent.BUY_ERROR_NOT_ENOUGH_MONEY then
dialogType = DialogElement.TYPE_WARNING
text = "rl_ui_semenPurchaseNoMoney"
elseif errorCode == AnimalBuyEvent.BUY_ERROR_NO_PERMISSION then
dialogType = DialogElement.TYPE_WARNING
text = "rl_ui_semenPurchaseNoPermission"
elseif errorCode ~= AnimalBuyEvent.BUY_SUCCESS then
dialogType = DialogElement.TYPE_WARNING
text = "rl_ui_semenPurchase_unsuccessful"
end
InfoDialog.show(g_i18n:getText(text), self.postSemenBought, self, dialogType, nil, nil, true)
end
function AnimalScreen:postSemenBought()
self.aiList:reloadData()
end
function RealisticLivestock_AnimalScreen:onMoneyChange()
if g_localPlayer == nil then return end
local farm = g_farmManager:getFarmById(g_localPlayer.farmId)
if farm.money <= -1 then
self.aiCurrentBalanceText:applyProfile(ShopMenu.GUI_PROFILE.SHOP_MONEY_NEGATIVE, nil, true)
else
self.aiCurrentBalanceText:applyProfile(ShopMenu.GUI_PROFILE.SHOP_MONEY, nil, true)
end
self.aiCurrentBalanceText:setText(g_i18n:formatMoney(farm.money, 0, true, false))
if self.aiShopMoneyBox ~= nil then
self.aiShopMoneyBox:invalidateLayout()
self.aiShopMoneyBoxBg:setSize(self.aiShopMoneyBox.flowSizes[1] + 60 * g_pixelSizeScaledX)
end
end
AnimalScreen.onMoneyChange = Utils.appendedFunction(AnimalScreen.onMoneyChange, RealisticLivestock_AnimalScreen.onMoneyChange)
function AnimalScreen:onClickChangeAIAnimalType(animalTypeIndex)
self.aiAnimalTypeIndex = animalTypeIndex
self.aiList:reloadData()
self:onAIListSelectionChanged()
end
function AnimalScreen:onAIListSelectionChanged()
self.aiInfoContainer:setVisible(false)
self.buttonBuyAI:setDisabled(true)
self.buttonFavourite:setDisabled(true)
if self.aiAnimals == nil or self.aiAnimalTypeIndex == nil or self.aiAnimals[self.aiAnimalTypeIndex] == nil then return end
local index = self.aiList.selectedIndex
local animal = self.aiAnimals[self.aiAnimalTypeIndex][index]
if animal == nil then return end
self.buttonBuyAI:setDisabled(false)
self.buttonFavourite:setDisabled(false)
local uniqueUserId = g_localPlayer:getUniqueId()
self.buttonFavourite:setText(g_i18n:getText("rl_ui_" .. (animal.favouritedBy[uniqueUserId] ~= nil and animal.favouritedBy[uniqueUserId] and "unFavourite" or "favourite")))
self.aiInfoContainer:setVisible(true)
self.aiSuccessValue:setText(string.format("%s%%", tostring(math.round(animal.success * 100))))
for i = 1, #self.aiGeneticsTitle do
self.aiGeneticsTitle[i]:setVisible(false)
self.aiGeneticsValue[i]:setVisible(false)
end
local i = 1
for key, value in pairs(animal.genetics) do
self.aiGeneticsTitle[i]:setVisible(true)
self.aiGeneticsValue[i]:setVisible(true)
local text
if value >= 1.65 then
text = "extremelyHigh"
elseif value >= 1.4 then
text = "veryHigh"
elseif value >= 1.1 then
text = "high"
elseif value >= 0.9 then
text = "average"
elseif value >= 0.7 then
text = "low"
elseif value >= 0.35 then
text = "veryLow"
else
text = "extremelyLow"
end
self.aiGeneticsTitle[i]:setText(g_i18n:getText("rl_ui_" .. key))
self.aiGeneticsValue[i]:setText(g_i18n:getText("rl_ui_genetics_" .. text))
i = i + 1
end
self.aiQuantitySelector:setState(1)
self:onClickChangeAIQuantity(1)
end
function AnimalScreen:onClickChangeAIQuantity(state)
local animal = self.aiAnimals[self.aiAnimalTypeIndex][self.aiList.selectedIndex]
local quantity = AnimalScreen.DEWAR_QUANTITIES[state]
local price = g_currentMission.animalSystem:getFarmSemenPrice(animal.birthday.country, animal.farmId) * quantity * animal.success * 2.25
for _, value in pairs(animal.genetics) do price = price * value end
self.aiQuantityPrice:setText(g_i18n:formatMoney(price * Dewar.PRICE_PER_STRAW, 2, true, true))
end
function AnimalScreen:onClickFavouriteAnimal()
local animal = self.aiAnimals[self.aiAnimalTypeIndex][self.aiList.selectedIndex]
if animal == nil then return end
local uniqueId = g_localPlayer:getUniqueId()
if animal.favouritedBy[uniqueId] == nil then
animal.favouritedBy[uniqueId] = true
else
animal.favouritedBy[uniqueId] = not animal.favouritedBy[uniqueId]
end
self.buttonFavourite:setText(g_i18n:getText("rl_ui_" .. (animal.favouritedBy[uniqueUserId] ~= nil and animal.favouritedBy[uniqueUserId] and "unFavourite" or "favourite")))
self.aiList:reloadData()
end
function AnimalScreen:onClickLogMode()
if self.husbandry == nil or not self.isDirectFarm then return end
self.filters = nil
self.filteredItems = nil
self.isInfoMode = false
self.isBuyMode = false
self.isLogMode = true
self.isHerdsmanMode = false
self.isAIMode = false
self.buttonBuySelected:setVisible(false)
self.buttonToggleSelectAll:setVisible(false)
self.buttonBuy:setVisible(false)
self.buttonMonitor:setVisible(false)
self.buttonArtificialInsemination:setVisible(false)
self.buttonMark:setVisible(false)
self.buttonRename:setVisible(false)
self.buttonSelect:setVisible(false)
self.buttonSell:setVisible(false)
self.buttonFilters:setVisible(false)
self.buttonDiseases:setVisible(false)
self.buttonDeleteMessage:setVisible(true)
self.buttonApplyHerdsmanSettings:setVisible(false)
self.buttonCastrate:setVisible(false)
self.buttonBuyAI:setVisible(false)
self.buttonFavourite:setVisible(false)
self.logContainer:setVisible(true)
self.herdsmanContainer:setVisible(false)
self.aiContainer:setVisible(false)
self.sourceBoxBg:setVisible(false)
self.mainContentContainer:setVisible(false)
self.tabBuy:setSelected(false)
self.tabSell:setSelected(false)
self.tabInfo:setSelected(false)
self.tabLog:setSelected(true)
self.tabHerdsman:setSelected(false)
self.tabAI:setSelected(false)
local allMessages = self.husbandry:getRLMessages()
local messages = { {} }
for i = #allMessages, 1, -1 do
local message = allMessages[i]
if #messages[#messages] >= 250 then table.insert(messages, { }) end
table.insert(messages[#messages], message)
end
self.messages, self.currentMessagePage = messages, 1
self:updateLog()
self.husbandry:setHasUnreadRLMessages(false)
self.buttonsPanel:invalidateLayout()
end
function AnimalScreen:onClickHerdsmanMode()
if self.husbandry == nil or not self.isDirectFarm then return end
self.filters = nil
self.filteredItems = nil
self.isInfoMode = false
self.isBuyMode = false
self.isLogMode = false
self.isHerdsmanMode = true
self.isAIMode = false
self.buttonBuySelected:setVisible(false)
self.buttonToggleSelectAll:setVisible(false)
self.buttonBuy:setVisible(false)
self.buttonMonitor:setVisible(false)
self.buttonArtificialInsemination:setVisible(false)
self.buttonMark:setVisible(false)
self.buttonRename:setVisible(false)
self.buttonSelect:setVisible(false)
self.buttonSell:setVisible(false)
self.buttonFilters:setVisible(false)
self.buttonDiseases:setVisible(false)
self.buttonDeleteMessage:setVisible(false)
self.buttonApplyHerdsmanSettings:setVisible(true)
self.buttonCastrate:setVisible(false)
self.buttonBuyAI:setVisible(false)
self.buttonFavourite:setVisible(false)
self.logContainer:setVisible(false)
self.herdsmanContainer:setVisible(true)
self.aiContainer:setVisible(false)
self.sourceBoxBg:setVisible(false)
self.mainContentContainer:setVisible(false)
self.herdsmanLoadProfileButton:setDisabled(not ProfileDialog.getHasProfiles())
self.tabBuy:setSelected(false)
self.tabSell:setSelected(false)
self.tabInfo:setSelected(false)
self.tabLog:setSelected(false)
self.tabHerdsman:setSelected(true)
self.tabAI:setSelected(false)
self.buttonsPanel:invalidateLayout()
self.currentHerdsmanPage = nil
local animalTypeIndex = self.husbandry:getAnimalTypeIndex()
local ageValues = {}
local ageTexts = {}
if animalTypeIndex == AnimalType.COW then
ageValues = { 6, 12, 18, 24, 30, 36, 48, 60, 72, 84, 96, 108, 120 }
elseif animalTypeIndex == AnimalType.SHEEP then
ageValues = { 3, 6, 9, 12, 18, 24, 30, 36, 48, 60, 72 }
elseif animalTypeIndex == AnimalType.PIG then
ageValues = { 3, 6, 9, 12, 18, 24, 30, 36, 48, 60, 72 }
elseif animalTypeIndex == AnimalType.HORSE then
ageValues = { 12, 24, 36, 48, 60, 90, 120, 150, 180, 240 }
elseif animalTypeIndex == AnimalType.CHICKEN then
ageValues = { 3, 6, 9, 12, 18, 24, 30, 36, 48, 60 }
end
table.insert(ageValues, 1, 0)
for _, value in pairs(ageValues) do table.insert(ageTexts, RealisticLivestock.formatAge(value)) end
table.insert(ageValues, 999)
table.insert(ageTexts, g_i18n:getText("rl_ui_infinite"))
self.herdsmanOptions["age"].values = ageValues
self.herdsmanOptions["age"].texts = ageTexts
local breeds = g_currentMission.animalSystem:getBreedsByAnimalTypeIndex(animalTypeIndex)
local breedsValues, breedsTexts = { "any" }, { "any" }
for breed, subTypes in pairs(breeds) do
table.insert(breedsValues, breed)
table.insert(breedsTexts, AnimalSystem.BREED_TO_NAME[breed] or breed)
end
self.herdsmanOptions["breed"].values = breedsValues
self.herdsmanOptions["breed"].texts = breedsTexts
local farmDewars = g_localPlayer ~= nil and g_dewarManager:getDewarsByFarm(g_localPlayer.farmId)
local dewars, dewarTexts, dewarValues = nil, { "any" }, { "any" }
if farmDewars ~= nil then
dewars = farmDewars[animalTypeIndex]
if dewars ~= nil then
for _, dewar in pairs(dewars) do
table.insert(dewarTexts, string.format("%s %s %s (%s %s)", RealisticLivestock.AREA_CODES[dewar.animal.country].code, dewar.animal.farmId, dewar.animal.uniqueId, dewar.straws, g_i18n:getText("rl_ui_straw" .. (dewar.straws == 1 and "Single" or "Multiple"))))
table.insert(dewarValues, dewar:getUniqueId())
end
end
end
self.herdsmanAIDewars = dewars
self.herdsmanOptions["semen"].values = dewarValues
self.herdsmanOptions["semen"].texts = dewarTexts
self:onClickHerdsmanPageBuy()
local wage = self.husbandry:getAIManager().wage or 0
self.herdsmanPreviousWageText:setText(g_i18n:formatMoney(wage, 2, true, true))
end
function AnimalScreen:onClickHerdsmanPageBuy()
if self.currentHerdsmanPage == "buy" then return end
self.herdsmanPageBuy:setVisible(true)
self.herdsmanPageSell:setVisible(false)
self.herdsmanPageCastrate:setVisible(false)
self.herdsmanPageNaming:setVisible(false)
self.herdsmanPageAI:setVisible(false)
self.herdsmanPageBuyButtonBg:setSelected(true)
self.herdsmanPageSellButtonBg:setSelected(false)
self.herdsmanPageCastrateButtonBg:setSelected(false)
self.herdsmanPageNamingButtonBg:setSelected(false)
self.herdsmanPageAIButtonBg:setSelected(false)
self.currentHerdsmanPage = "buy"
self:setDefaultHerdsmanOptions()
end
function AnimalScreen:onClickHerdsmanPageSell()
if self.currentHerdsmanPage == "sell" then return end
self.herdsmanPageBuy:setVisible(false)
self.herdsmanPageSell:setVisible(true)
self.herdsmanPageCastrate:setVisible(false)
self.herdsmanPageNaming:setVisible(false)
self.herdsmanPageAI:setVisible(false)
self.herdsmanPageBuyButtonBg:setSelected(false)
self.herdsmanPageSellButtonBg:setSelected(true)
self.herdsmanPageCastrateButtonBg:setSelected(false)
self.herdsmanPageNamingButtonBg:setSelected(false)
self.herdsmanPageAIButtonBg:setSelected(false)
self.currentHerdsmanPage = "sell"
self:setDefaultHerdsmanOptions()
end
function AnimalScreen:onClickHerdsmanPageCastrate()
if self.currentHerdsmanPage == "castrate" then return end
self.herdsmanPageBuy:setVisible(false)
self.herdsmanPageSell:setVisible(false)
self.herdsmanPageCastrate:setVisible(true)
self.herdsmanPageNaming:setVisible(false)
self.herdsmanPageAI:setVisible(false)
self.herdsmanPageBuyButtonBg:setSelected(false)
self.herdsmanPageSellButtonBg:setSelected(false)
self.herdsmanPageCastrateButtonBg:setSelected(true)
self.herdsmanPageNamingButtonBg:setSelected(false)
self.herdsmanPageAIButtonBg:setSelected(false)
self.currentHerdsmanPage = "castrate"
self:setDefaultHerdsmanOptions()
end
function AnimalScreen:onClickHerdsmanPageNaming()
if self.currentHerdsmanPage == "naming" then return end
self.herdsmanPageBuy:setVisible(false)
self.herdsmanPageSell:setVisible(false)
self.herdsmanPageCastrate:setVisible(false)
self.herdsmanPageNaming:setVisible(true)
self.herdsmanPageAI:setVisible(false)
self.herdsmanPageBuyButtonBg:setSelected(false)
self.herdsmanPageSellButtonBg:setSelected(false)
self.herdsmanPageCastrateButtonBg:setSelected(false)
self.herdsmanPageNamingButtonBg:setSelected(true)
self.herdsmanPageAIButtonBg:setSelected(false)
self.currentHerdsmanPage = "naming"
self:setDefaultHerdsmanOptions()
end
function AnimalScreen:onClickHerdsmanPageAI()
if self.currentHerdsmanPage == "ai" then return end
self.herdsmanPageBuy:setVisible(false)
self.herdsmanPageSell:setVisible(false)
self.herdsmanPageCastrate:setVisible(false)
self.herdsmanPageNaming:setVisible(false)
self.herdsmanPageAI:setVisible(true)
self.herdsmanPageBuyButtonBg:setSelected(false)
self.herdsmanPageSellButtonBg:setSelected(false)
self.herdsmanPageCastrateButtonBg:setSelected(false)
self.herdsmanPageNamingButtonBg:setSelected(false)
self.herdsmanPageAIButtonBg:setSelected(true)
self.currentHerdsmanPage = "ai"
self:setDefaultHerdsmanOptions()
end
function AnimalScreen:onClickHerdsmanSaveProfile()
ProfileDialog.show("save", self.husbandry:getAIManager(), self.onHerdsmanSaveProfileCallback, self)
end
function AnimalScreen:onClickHerdsmanLoadProfile()
ProfileDialog.show("load", self.husbandry:getAIManager(), self.onHerdsmanLoadProfileCallback, self)
end
function AnimalScreen:onHerdsmanSaveProfileCallback()
self.herdsmanLoadProfileButton:setDisabled(not ProfileDialog.getHasProfiles())
end
function AnimalScreen:onHerdsmanLoadProfileCallback()
self.herdsmanLoadProfileButton:setDisabled(not ProfileDialog.getHasProfiles())
self:setDefaultHerdsmanOptions()
end
function AnimalScreen:onClickEnableHerdsman(state, button)
if self.currentHerdsmanPage == "castrate" and self.husbandry:getAnimalTypeIndex() == AnimalType.CHICKEN then
button:setState(1)
return
end
local option = self.herdsmanOptions.enabled
local enabled = option.values[state]
local page = self.herdsmanPages[self.currentHerdsmanPage]
for _, element in pairs(page.elements) do
element:setDisabled(not enabled and element.name ~= "enabled")
end
end
function AnimalScreen:setDefaultHerdsmanOptions()
local container = self.herdsmanPages[self.currentHerdsmanPage]
local settings = table.clone(self.husbandry:getAIManager():getSettings(), 5)
self.aiManagerSettings = settings
for _, element in pairs(container.elements) do
element:setDisabled(not settings[self.currentHerdsmanPage]["enabled"] and element.name ~= "enabled")
if element.name == "ignore" then continue end
local option = self.herdsmanOptions[element.name]
local value
if string.contains(option.target, "|") then
local paths = string.split(option.target, "|")
value = self.aiManagerSettings[self.currentHerdsmanPage][paths[1]]
for i = 2, #paths do value = value[paths[i]] end
else
value = self.aiManagerSettings[self.currentHerdsmanPage][option.target]
end
if option.ignoreTexts then
element:setTexts(option.texts)
end
if option.type == "doubleSlider" then
for i, state in pairs(option.values) do
if state == value.min then
element.leftState = i
elseif state == value.max then
element.rightState = i
end
end
element:updateSlider()
element:updateFillingBar()
element:updateContentElement()
elseif option.type == "input" then
element:setText(tostring(value))
else
for i, state in pairs(option.values) do
if state == value then
element:setState(i)
break
end
end
end
if element.name == "budget|type" then self:onClickChangeHerdsmanBudgetType(element:getState(), element) end
end
end
function AnimalScreen:onClickApplyHerdsmanSettings()
if self.currentHerdsmanPage == nil then return end
local settings = self.aiManagerSettings[self.currentHerdsmanPage]
local page = self.herdsmanPages[self.currentHerdsmanPage]
for _, element in pairs(page.elements) do
if element.name == "ignore" then continue end
local option = self.herdsmanOptions[element.name]
if option.type == "binary" or option.type == "tripleOption" or option.type == "input" or option.type == "multi" then
local value
if option.type == "input" then
value = tonumber(element:getText()) or 0
else
value = option.values[element:getState()]
end
if string.contains(option.target, "|") then
local paths = string.split(option.target, "|")
if #paths == 2 then
settings[paths[1]][paths[2]] = value
elseif #paths == 3 then
settings[paths[1]][paths[2]][paths[3]] = value
end
else
settings[option.target] = value
end
elseif option.type == "doubleSlider" then
settings[option.target].min = option.values[element:getLowestState()]
settings[option.target].max = option.values[element:getHighestState()]
end
end
self.husbandry:getAIManager():setSettings(self.aiManagerSettings[self.currentHerdsmanPage], self.currentHerdsmanPage)
end
function AnimalScreen:onHerdsmanTextChangedInt(element, text)
text = string.gsub(text, "[%a%p]", "")
element:setText(text)
end
function AnimalScreen:onClickChangeHerdsmanBudgetType(state, button)
local value = self.herdsmanOptions[button.name].values[state]
for _, element in pairs(button.parent.elements) do
if element.name == "budget|fixed" then element:setVisible(value == "fixed") end
if element.name == "budget|percentage" then element:setVisible(value == "percentage") end
end
button.parent:invalidateLayout()
end
function AnimalScreen:onClickDiseases()
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
if item == nil or (item.cluster == nil and item.animal == nil) then return end
local animal = item.animal or item.cluster
DiseaseDialog.show(animal)
end
function RealisticLivestock_AnimalScreen:onClickBuyMode(a, b)
self.isInfoMode = false
self.isLogMode = false
self.isHerdsmanMode = false
self.isAIMode = false
self.selectedItems = {}
self.pendingBulkTransaction = nil
self.filters = nil
self.filteredItems = nil
self.buttonToggleSelectAll:setVisible(true)
self.buttonToggleSelectAll:setText(g_i18n:getText("rl_ui_selectAll"))
self.buttonBuySelected:setText(self.isTrailerFarm and g_i18n:getText("rl_ui_moveSelected") or g_i18n:getText("rl_ui_buySelected"))
self.buttonCastrate:setVisible(false)
self.buttonDeleteMessage:setVisible(false)
self.buttonDiseases:setVisible(false)
self.buttonFilters:setVisible(true)
self.buttonApplyHerdsmanSettings:setVisible(false)
self.buttonBuyAI:setVisible(false)
self.buttonFavourite:setVisible(false)
self.logContainer:setVisible(false)
self.herdsmanContainer:setVisible(false)
self.aiContainer:setVisible(false)
self.sourceBoxBg:setVisible(true)
self.tabListContainer:setVisible(true)
self.mainContentContainer:setVisible(true)
self.buttonsPanel:invalidateLayout()
end
AnimalScreen.onClickBuyMode = Utils.prependedFunction(AnimalScreen.onClickBuyMode, RealisticLivestock_AnimalScreen.onClickBuyMode)
function RealisticLivestock_AnimalScreen:onClickSellMode(a, b)
self.isInfoMode = false
self.isLogMode = false
self.isHerdsmanMode = false
self.isAIMode = false
self.selectedItems = {}
self.pendingBulkTransaction = nil
self.filters = nil
self.filteredItems = nil
self.buttonToggleSelectAll:setVisible(true)
self.buttonToggleSelectAll:setText(g_i18n:getText("rl_ui_selectAll"))
self.buttonBuySelected:setText(self.isTrailerFarm and g_i18n:getText("rl_ui_moveSelected") or g_i18n:getText("rl_ui_sellSelected"))
self.buttonCastrate:setVisible(false)
self.buttonDeleteMessage:setVisible(false)
self.buttonDiseases:setVisible(false)
self.buttonFilters:setVisible(true)
self.buttonApplyHerdsmanSettings:setVisible(false)
self.buttonBuyAI:setVisible(false)
self.buttonFavourite:setVisible(false)
self.logContainer:setVisible(false)
self.herdsmanContainer:setVisible(false)
self.aiContainer:setVisible(false)
self.sourceBoxBg:setVisible(true)
self.tabListContainer:setVisible(true)
self.mainContentContainer:setVisible(true)
self.buttonsPanel:invalidateLayout()
end
AnimalScreen.onClickSellMode = Utils.prependedFunction(AnimalScreen.onClickSellMode, RealisticLivestock_AnimalScreen.onClickSellMode)
function RealisticLivestock_AnimalScreen:onPageNext(superFunc)
if self.isBuyMode then
self:onClickSellMode()
elseif not self.isInfoMode and not self.isLogMode and not self.isHerdsmanMode and not self.isAIMode then
self:onClickInfoMode()
elseif self.isInfoMode then
self:onClickAIMode()
elseif self.isAIMode then
self:onClickLogMode()
elseif self.isLogMode then
self:onClickHerdsmanMode()
else
self:onClickBuyMode()
end
end
AnimalScreen.onPageNext = Utils.overwrittenFunction(AnimalScreen.onPageNext, RealisticLivestock_AnimalScreen.onPageNext)
function RealisticLivestock_AnimalScreen:onPagePrevious(superFunc)
if self.isBuyMode then
self:onClickHerdsmanMode()
elseif self.isHerdsmanMode then
self:onClickLogMode()
elseif self.isLogMode then
self:onClickAIMode()
elseif self.isAIMode then
self:onClickInfoMode()
elseif not self.isInfoMode then
self:onClickBuyMode()
else
self:onClickSellMode()
end
end
AnimalScreen.onPagePrevious = Utils.overwrittenFunction(AnimalScreen.onPagePrevious, RealisticLivestock_AnimalScreen.onPagePrevious)
function AnimalScreen:onClickMark()
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
if item == nil or (item.cluster == nil and item.animal == nil) then return end
local animal = item.animal or item.cluster
local isMarked = not animal:getMarked()
if isMarked then
animal:setMarked("PLAYER", true)
else
animal:setMarked(nil, false)
end
self.sourceList:reloadData()
end
function RealisticLivestock_AnimalScreen:onClickRename()
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
if item == nil or (item.cluster == nil and item.animal == nil) then return end
local animal = item.animal or item.cluster
local dialog = NameInputDialog.INSTANCE
local name = animal.name or g_currentMission.animalNameSystem:getRandomName(animal.gender)
dialog:setCallback(self.changeName, self, name, nil, 30, nil, animal.gender)
g_gui:showDialog("NameInputDialog")
end
AnimalScreen.onClickRename = RealisticLivestock_AnimalScreen.onClickRename
function RealisticLivestock_AnimalScreen:changeName(text, clickOk)
if clickOk then
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
local animal = item.animal or item.cluster
if animal ~= nil then
text = text ~= "" and text or nil
if text ~= nil or animal.name ~= nil then
if text == nil then
animal:addMessage("NAME_DELETED", { animal.name })
elseif animal.name == nil then
animal:addMessage("NAME_ADDED", { text })
elseif animal.name ~= text then
animal:addMessage("NAME_CHANGE", { animal.name, text })
end
end
animal.name = text
animal:updateVisualRightEarTag()
AnimalNameChangeEvent.sendEvent(animal.clusterSystem.owner, animal, text)
end
g_animalScreen:updateInfoBox()
end
end
AnimalScreen.changeName = RealisticLivestock_AnimalScreen.changeName
-- #################################################################################
-- NOTES:
-- sourceList:setSelectedItem() changes the selected animal in the leftmost animal list
-- targetSelector buttons change the arrow buttons visibility at the top
-- #################################################################################
function RealisticLivestock_AnimalScreen:onClickAnimalInfo(button)
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
if item == nil then return end
local animal = item.animal or item.cluster
if animal == nil then return end
local animalType = animal.animalTypeIndex
if button.id == "childInfoButton" then
local children = animal.children
if children == nil or #children == 0 then return end
AnimalInfoDialog.show(children[1].farmId, children[1].uniqueId, children, animalType)
return
end
local target = button.id == "motherInfoButton" and "mother" or "father"
if target == nil then return end
local uniqueId = animal[target .. "Id"]
if uniqueId == "-1" then return end
local farmId = ""
local i = string.find(uniqueId, " ")
farmId = string.sub(uniqueId, 1, i - 1)
uniqueId = string.sub(uniqueId, i + 1)
if uniqueId == nil or farmId == nil then return end
AnimalInfoDialog.show(farmId, uniqueId, nil, animalType, animal:getIdentifiers())
end
AnimalScreen.onClickAnimalInfo = RealisticLivestock_AnimalScreen.onClickAnimalInfo
function RealisticLivestock_AnimalScreen:onClickInfoMode(a, b)
self.filters = nil
self.filteredItems = nil
self.isInfoMode = true
self.isBuyMode = false
self.isLogMode = false
self.isHerdsmanMode = false
self.isAIMode = false
self.buttonToggleSelectAll:setVisible(false)
self.buttonDeleteMessage:setVisible(false)
self.buttonFilters:setVisible(true)
self.buttonDiseases:setVisible(true)
self.buttonBuyAI:setVisible(false)
self.buttonFavourite:setVisible(false)
self.buttonApplyHerdsmanSettings:setVisible(false)
self.targetSelector.leftButtonElement:setVisible(false)
self.targetSelector.rightButtonElement:setVisible(false)
self:initSubcategories()
self.sourceList:setSelectedItem(1, 1, nil, true)
self.sourceSelector:setState(1, true)
self.isAutoUpdatingList = a
self:updateScreen()
self.isAutoUpdatingList = false
self:setSelectionState(AnimalScreen.SELECTION_SOURCE, true)
self.logContainer:setVisible(false)
self.herdsmanContainer:setVisible(false)
self.aiContainer:setVisible(false)
self.sourceBoxBg:setVisible(true)
self.tabListContainer:setVisible(true)
self.mainContentContainer:setVisible(true)
self.buttonsPanel:invalidateLayout()
end
AnimalScreen.onClickInfoMode = RealisticLivestock_AnimalScreen.onClickInfoMode
function AnimalScreen:onClickArtificialInsemination()
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
if item == nil or g_localPlayer == nil then return end
local animal = item.animal or item.cluster
if animal == nil then return end
AnimalAIDialog.show(g_localPlayer.farmId, animal.animalTypeIndex, animal)
end
function AnimalScreen:onClickMonitor()
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
if item == nil then return end
local animal = item.animal or item.cluster
if animal == nil then return end
local monitor = animal.monitor
monitor.active = not monitor.active
monitor.removed = not monitor.active
animal:updateVisualMonitor()
AnimalMonitorEvent.sendEvent(animal.clusterSystem.owner, animal, monitor.active, monitor.removed)
self.buttonMonitor:setText(g_i18n:getText("rl_ui_" .. (monitor.active and "remove" or "apply") .. "Monitor"))
self.buttonMonitor:setDisabled(monitor.removed)
self:updateInfoBox()
end
function AnimalScreen:onClickCastrate()
self.buttonCastrate:setDisabled(true)
local item = (self.filteredItems == nil and self.controller:getTargetItems() or self.filteredItems)[self.sourceList.selectedIndex]
if item == nil then return end
local animal = item.animal or item.cluster
if animal == nil then return end
animal.isCastrated = true
animal.genetics.fertility = 0
end
function RealisticLivestock_AnimalScreen:onListSelectionChanged(superFunc, list)
if list == self.sourceList or list == self.targetList then
superFunc(self, list)
else
self:onAIListSelectionChanged()
end
end
AnimalScreen.onListSelectionChanged = Utils.overwrittenFunction(AnimalScreen.onListSelectionChanged, RealisticLivestock_AnimalScreen.onListSelectionChanged)
function RealisticLivestock_AnimalScreen:updateInfoBox(superFunc, isSourceSelected)
if not g_gui.currentlyReloading then
--if isSourceSelected == nil then
--local _ = self.isSourceSelected
--end
local animalType = self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()]
local item
self.buttonCastrate:setVisible(false)
self.buttonMark:setVisible(false)
if self.filteredItems == nil then
if self.isBuyMode then
item = self.controller:getSourceItems(animalType, self.isBuyMode)[self.sourceList.selectedIndex]
else
item = self.controller:getTargetItems()[self.sourceList.selectedIndex]
end
else
item = self.filteredItems[self.sourceList.selectedIndex]
end
self.infoIcon:setVisible(item ~= nil)
self.infoName:setVisible(item ~= nil)
if item ~= nil then
self.detailsContainer:setVisible(true)
local animal = item.animal or item.cluster
self.inputBox:setVisible(self.isInfoMode and (animal.monitor.active or animal.monitor.removed))
self.outputBox:setVisible(self.isInfoMode and (animal.monitor.active or animal.monitor.removed))
self.infoIcon:setImageFilename(item:getFilename())
self.infoDescription:setText(item:getDescription())
local subType = g_currentMission.animalSystem:getSubTypeByIndex(item:getSubTypeIndex())
local fillTypeTitle = g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)
self.infoName:setText(fillTypeTitle)
local infos = item:getInfos()
for k, infoTitle in ipairs(self.infoTitle) do
local info = infos[k]
local infoValue = self.infoValue[k]
infoTitle:setVisible(info ~= nil)
infoValue:setVisible(info ~= nil)
if info ~= nil then
infoTitle:setText(info.title)
infoValue:setText(info.value)
if info.colour ~= nil then
infoTitle:setTextColor(info.colour[1], info.colour[2], info.colour[3], 1)
infoValue:setTextColor(info.colour[1], info.colour[2], info.colour[3], 1)
else
infoTitle:setTextColor(1, 1, 1, 1)
infoValue:setTextColor(1, 1, 1, 1)
end
end
end
self.diseasesBox:setVisible(not self.isInfoMode)
if not self.isInfoMode then
local diseases = animal.diseases
for i = 1, #self.diseasesTitle do
self.diseasesTitle[i]:setVisible(false)
self.diseasesValue[i]:setVisible(false)
end
for i, disease in pairs(diseases) do
self.diseasesTitle[i]:setVisible(true)
self.diseasesValue[i]:setVisible(true)
self.diseasesTitle[i]:setText(disease.type.name)
self.diseasesValue[i]:setText(disease:getStatus())
end
end
self.geneticsBox:applyProfile(self.isInfoMode and "rl_geneticsBoxInfo" or "rl_geneticsBox")
self.geneticsBox:setVisible(true)
local genetics = animal:addGeneticsInfo()
for i, title in ipairs(self.geneticsTitle) do
local value = self.geneticsValue[i]
title:setVisible(genetics[i] ~= nil)
value:setVisible(genetics[i] ~= nil)
if genetics[i] == nil then continue end
title:setText(genetics[i].title)
value:setText(g_i18n:getText(genetics[i].text))
local quality = genetics[i].text
if quality == "rl_ui_genetics_infertile" then
title:setTextColor(1, 0, 0, 1)
value:setTextColor(1, 0, 0, 1)
elseif quality == "rl_ui_genetics_extremelyLow" or quality == "rl_ui_genetics_extremelyBad" then
title:setTextColor(1, 0, 0, 1)
value:setTextColor(1, 0, 0, 1)
elseif quality == "rl_ui_genetics_veryLow" or quality == "rl_ui_genetics_veryBad" then
title:setTextColor(1, 0.2, 0, 1)
value:setTextColor(1, 0.2, 0, 1)
elseif quality == "rl_ui_genetics_low" or quality == "rl_ui_genetics_bad" then
title:setTextColor(1, 0.52, 0, 1)
value:setTextColor(1, 0.52, 0, 1)
elseif quality == "rl_ui_genetics_average" then
title:setTextColor(1, 1, 0, 1)
value:setTextColor(1, 1, 0, 1)
elseif quality == "rl_ui_genetics_high" or quality == "rl_ui_genetics_good" then
title:setTextColor(0.52, 1, 0, 1)
value:setTextColor(0.52, 1, 0, 1)
elseif quality == "rl_ui_genetics_veryHigh" or quality == "rl_ui_genetics_veryGood" then
title:setTextColor(0.2, 1, 0, 1)
value:setTextColor(0.2, 1, 0, 1)
else
title:setTextColor(0, 1, 0, 1)
value:setTextColor(0, 1, 0, 1)
end
end
if self.isInfoMode then
local isMarked = animal:getMarked()
self.buttonMark:setVisible(true)
self.buttonMark:setText(isMarked and g_i18n:getText("rl_ui_unmark") or g_i18n:getText("rl_ui_mark"))
if animal.gender == "male" and animal.animalTypeIndex ~= AnimalType.CHICKEN then
self.buttonCastrate:setVisible(true)
self.buttonCastrate:setDisabled(animal.isCastrated)
end
self.buttonMonitor:setText(g_i18n:getText("rl_ui_" .. (animal.monitor.active and "remove" or "apply") .. "Monitor"))
self.buttonMonitor:setDisabled(animal.monitor.removed)
self.motherInfoButton:setDisabled(animal.motherId == nil or animal.motherId == "-1")
self.motherInfoButton:setText(g_i18n:getText("rl_ui_mother") .. " (" .. ((animal.motherId == nil or animal.motherId == "-1") and g_i18n:getText("rl_ui_unknown") or animal.motherId) .. ")")
self.fatherInfoButton:setDisabled(animal.fatherId == nil or animal.fatherId == "-1")
self.fatherInfoButton:setText(g_i18n:getText("rl_ui_father") .. " (" .. ((animal.fatherId == nil or animal.fatherId == "-1") and g_i18n:getText("rl_ui_unknown") or animal.fatherId) .. ")")
self.childInfoButton:setDisabled(not animal.isParent)
for i = 1, #self.inputTitle do
self.inputTitle[i]:setVisible(false)
self.inputValue[i]:setVisible(false)
end
for i = 1, #self.outputTitle do
self.outputTitle[i]:setVisible(false)
self.outputValue[i]:setVisible(false)
end
local infoIndex = 1
local daysPerMonth = g_currentMission.environment.daysPerPeriod
for fillType, amount in pairs(animal.input) do
if infoIndex > #self.inputTitle then break end
local title, value = self.inputTitle[infoIndex], self.inputValue[infoIndex]
title:setVisible(true)
value:setVisible(true)
title:setText(g_i18n:getText("rl_ui_input_" .. fillType))
value:setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (amount * 24) / daysPerMonth))
infoIndex = infoIndex + 1
end
infoIndex = 1
for fillType, amount in pairs(animal.output) do
if infoIndex > #self.outputTitle then break end
local title, value = self.outputTitle[infoIndex], self.outputValue[infoIndex]
title:setVisible(true)
value:setVisible(true)
local outputText = fillType
if fillType == "pallets" then
if animal.animalTypeIndex == AnimalType.COW then outputText = "pallets_milk" end
if animal.animalTypeIndex == AnimalType.SHEEP then outputText = animal.subType == "GOAT" and "pallets_goatMilk" or "pallets_wool" end
if animal.animalTypeIndex == AnimalType.CHICKEN then outputText = "pallets_eggs" end
end
title:setText(g_i18n:getText("rl_ui_output_" .. outputText))
value:setText(string.format(g_i18n:getText("rl_ui_amountPerDay"), (amount * 24) / daysPerMonth))
infoIndex = infoIndex + 1
end
end
if not Platform.isMobile then self:updatePrice() end
self.infoBox:setVisible(not self.isInfoMode)
--self.numAnimalsBox:setVisible(not self.isInfoMode)
self.parentBox:setVisible(self.isInfoMode and not self.isBuyMode)
self.buttonRename:setVisible(self.isInfoMode)
else
self.detailsContainer:setVisible(false)
self.buttonRename:setVisible(false)
end
end
self.numAnimalsBox:setVisible(false)
self.buttonsPanel:invalidateLayout()
end
AnimalScreen.updateInfoBox = Utils.overwrittenFunction(AnimalScreen.updateInfoBox, RealisticLivestock_AnimalScreen.updateInfoBox)
function RealisticLivestock_AnimalScreen:updateScreen(superFunc, state)
self.isAutoUpdatingList = true
self.sourceList:reloadData(true)
self.isAutoUpdatingList = false
local placeables, targetText
if self.isBuyMode then
placeables, targetText = self.controller:getSourceData(self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()])
else
placeables, targetText = self.controller:getTargetData(self.sourceSelector:getState())
end
self.targetText:setText(targetText)
self.targetItems = placeables
local husbandryTexts = {}
for _, placeable in pairs(placeables) do
local animalType = g_currentMission.animalSystem:getTypeByIndex(self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()])
local maxAnimalString = " (" .. placeable:getNumOfAnimals() .. "/" .. placeable:getMaxNumOfAnimals(animalType) .. ")"
local husbandryString = placeable:getName() .. maxAnimalString
table.insert(husbandryTexts, husbandryString)
end
self.targetSelector:setTexts(husbandryTexts)
if #placeables > 0 and (not state or self.targetSelector:getState() == 0) then
self.targetSelector:setState(1)
end
self:onTargetSelectionChanged(true)
self:setSelectionState(AnimalScreen.SELECTION_SOURCE)
local hasAnimals = self.sourceList:getItemCount() > 0
self.detailsContainer:setVisible(hasAnimals)
self.infoBox:setVisible(not self.isInfoMode)
--self.numAnimalsBox:setVisible(not self.isInfoMode)
self.numAnimalsBox:setVisible(false)
self.parentBox:setVisible(self.isInfoMode)
self.geneticsBox:setVisible(self.isInfoMode)
if self.isInfoMode then
self.buttonBuy:setVisible(false)
self.buttonSell:setVisible(false)
else
local isItemSelected = self.numAnimalsElement:getIsFocused()
self.buttonBuy:setVisible(self.isBuyMode and isItemSelected)
self.buttonSell:setVisible(isItemSelected and not self.isBuyMode)
self.buttonSelect:setVisible(not isItemSelected)
end
self.buttonBuy:setDisabled(not self.isBuyMode)
self.buttonBuy:setVisible(not self.isInfoMode and self.isBuyMode)
self.buttonSell:setDisabled(self.isInfoMode or self.isBuyMode)
self.buttonSell:setVisible(not self.isInfoMode and not self.isBuyMode)
self.buttonRename:setVisible(self.isInfoMode)
self.buttonMonitor:setVisible(self.isInfoMode)
self.buttonArtificialInsemination:setVisible(self.isInfoMode)
if hasAnimals then
self:updatePrice()
self:updateInfoBox()
end
self.tabBuy:setSelected(self.isBuyMode and not self.isInfoMode)
self.tabSell:setSelected(not self.isBuyMode and not self.isInfoMode and not self.isLogMode and not self.isHerdsmanMode)
self.tabInfo:setSelected(not self.isBuyMode and self.isInfoMode)
self.tabLog:setSelected(self.isLogMode)
self.tabHerdsman:setSelected(self.isHerdsmanMode)
self.tabAI:setSelected(self.isAIMode)
self.buttonBuySelected:setVisible(not self.isTrailer and not self.isInfoMode)
self.buttonsPanel:invalidateLayout()
end
AnimalScreen.updateScreen = Utils.overwrittenFunction(AnimalScreen.updateScreen, RealisticLivestock_AnimalScreen.updateScreen)
function RealisticLivestock_AnimalScreen:setMaxNumAnimals()
self.infoBox:setVisible(not self.isInfoMode)
--self.numAnimalsBox:setVisible(not self.isInfoMode)
self.numAnimalsBox:setVisible(false)
self.parentBox:setVisible(self.isInfoMode and not self.isBuyMode)
self.geneticsBox:setVisible(self.isInfoMode)
end
AnimalScreen.setMaxNumAnimals = Utils.appendedFunction(AnimalScreen.setMaxNumAnimals, RealisticLivestock_AnimalScreen.setMaxNumAnimals)
function RealisticLivestock_AnimalScreen:getCellTypeForItemInSection(_, list, _, index)
if list == self.aiList then
local animals = self.aiAnimals[self.aiAnimalTypeIndex]
local a = animals[index]
local b = animals[index - 1]
return (a == nil or b == nil or a:getSubTypeIndex() ~= b:getSubTypeIndex()) and "sectionCell" or "defaultCell"
end
if list ~= self.sourceList then return nil end
local animalTypeIndex = self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()]
local items
if self.filteredItems == nil then
if self.isInfoMode or not self.isBuyMode then
items = self.controller:getTargetItems()
else
items = self.controller:getSourceItems(animalTypeIndex, self.isBuyMode)
end
else
items = self.filteredItems
end
local a = items[index]
local b = items[index - 1]
if a ~= nil and a:getHasAnyDisease() and index == 1 then return "sectionCell" end
if a ~= nil and b ~= nil and b:getHasAnyDisease() then return a:getHasAnyDisease() and "defaultCell" or "sectionCell" end
return (a == nil or b == nil or a:getSubTypeIndex() ~= b:getSubTypeIndex()) and "sectionCell" or "defaultCell"
end
AnimalScreen.getCellTypeForItemInSection = Utils.overwrittenFunction(AnimalScreen.getCellTypeForItemInSection, RealisticLivestock_AnimalScreen.getCellTypeForItemInSection)
function RealisticLivestock_AnimalScreen:getNumberOfItemsInSection(superFunc, list)
if list == self.aiList then return #self.aiAnimals[self.aiAnimalTypeIndex] end
if self.isLogMode then return #self.messages[self.currentMessagePage] end
if self.filteredItems == nil or not self.isOpen then return superFunc(self, list) end
return #self.filteredItems
end
AnimalScreen.getNumberOfItemsInSection = Utils.overwrittenFunction(AnimalScreen.getNumberOfItemsInSection, RealisticLivestock_AnimalScreen.getNumberOfItemsInSection)
function RealisticLivestock_AnimalScreen:populateCellForItemInSection(_, list, _, index, cell)
if list == self.aiList then
local animal = self.aiAnimals[self.aiAnimalTypeIndex][index]
if animal == nil then return end
local subType = animal:getSubType()
if cell.name == "sectionCell" then cell:getAttribute("title"):setText(g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)) end
local name = animal:getName()
if name == nil or name == "" then name = string.format("%s %s %s", RealisticLivestock.AREA_CODES[animal.birthday.country].code, animal.farmId, animal.uniqueId) end
local visual = g_currentMission.animalSystem:getVisualByAge(animal.subTypeIndex, animal.age)
cell:getAttribute("name"):setText(name)
cell:getAttribute("icon"):setImageFilename(visual.store.imageFilename)
local genetics = 0
local numGenetics = 0
for _, value in pairs(animal.genetics) do
genetics = genetics + value
numGenetics = numGenetics + 1
end
local avgGenetics = (numGenetics > 0 and genetics / numGenetics) or 0
local geneticsText = "extremelyBad"
if avgGenetics >= 1.65 then
geneticsText = "extremelyGood"
elseif avgGenetics >= 1.35 then
geneticsText = "veryGood"
elseif avgGenetics >= 1.15 then
geneticsText = "good"
elseif avgGenetics >= 0.85 then
geneticsText = "average"
elseif avgGenetics >= 0.65 then
geneticsText = "bad"
elseif avgGenetics >= 0.35 then
geneticsText = "veryBad"
end
cell:getAttribute("price"):setText(g_i18n:getText("rl_ui_genetics_" .. geneticsText))
local uniqueUserId = g_localPlayer:getUniqueId()
local isFavourite = animal.favouritedBy[uniqueUserId] ~= nil and animal.favouritedBy[uniqueUserId]
if cell.name == "defaultCell" then
if isFavourite then
cell:setImageColor(GuiOverlay.STATE_NORMAL, 1, 0.2, 0)
else
cell:setImageColor(GuiOverlay.STATE_NORMAL, 1, 1, 1)
end
else
local background = cell:getAttribute("background")
if isFavourite then
background:setImageColor(GuiOverlay.STATE_NORMAL, 1, 0.2, 0)
else
background:setImageColor(GuiOverlay.STATE_NORMAL, 1, 1, 1)
end
end
return
end
if list == self.husbandryList then
local messagePage = self.messages[self.currentMessagePage]
if messagePage == nil then return end
local message = messagePage[index]
if message == nil then return end
local baseMessage = RLMessage[message.id]
local text, argI = string.split(g_i18n:getText("rl_message_" .. baseMessage.text), " "), 1
for i, split in pairs(text) do
if split == "%s" then
if string.contains(message.args[argI], "rl_") then
text[i] = g_i18n:getText(message.args[argI])
else
text[i] = message.args[argI]
end
argI = argI + 1
elseif split == "'%s'" then
if string.contains(message.args[argI], "rl_") then
text[i] = "'" .. g_i18n:getText(message.args[argI]) .. "'"
else
text[i] = "'" .. message.args[argI] .. "'"
end
argI = argI + 1
end
end
text = table.concat(text, " ")
cell:getAttribute("message"):setText(text)
cell:getAttribute("type"):setText(g_i18n:getText("rl_messageTitle_" .. baseMessage.title))
cell:getAttribute("date"):setText(message.date)
cell:getAttribute("animal"):setText(message.animal or "N/A")
cell:getAttribute("importance"):setImageSlice(nil, "realistic_livestock.importance_" .. baseMessage.importance)
return
end
local animalType = self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()]
local filteredItems = self.filteredItems
if list == self.sourceList then
local item
if filteredItems == nil then
if self.isBuyMode then
item = self.controller:getSourceItems(animalType, self.isBuyMode)[index]
else
item = self.controller:getTargetItems()[index]
end
else
item = filteredItems[index]
end
if item == nil then return end
local animal = item.animal or item.cluster
local subType = g_currentMission.animalSystem:getSubTypeByIndex(item:getSubTypeIndex())
self.isHorse = subType.typeIndex == AnimalType.HORSE
local isDiseased = animal:getHasAnyDisease()
if cell.name == "sectionCell" then cell:getAttribute("title"):setText(isDiseased and g_i18n:getText("rl_ui_diseasedAnimals") or g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)) end
self.isHorse = g_currentMission.animalSystem:getSubTypeByIndex(item:getSubTypeIndex()).typeIndex == AnimalType.HORSE
local name = item:getName()
local isMarked = animal:getMarked()
local recentlyBoughtByAI = animal:getRecentlyBoughtByAI()
if cell.name == "defaultCell" then
if isDiseased then
cell:setImageColor(GuiOverlay.STATE_NORMAL, 1, 0.08, 0)
elseif isMarked then
cell:setImageColor(GuiOverlay.STATE_NORMAL, 1, 0.2, 0)
else
cell:setImageColor(GuiOverlay.STATE_NORMAL, 1, 1, 1)
end
else
local background = cell:getAttribute("background")
if isDiseased then
background:setImageColor(GuiOverlay.STATE_NORMAL, 1, 0.08, 0)
elseif isMarked then
background:setImageColor(GuiOverlay.STATE_NORMAL, 1, 0.2, 0)
else
background:setImageColor(GuiOverlay.STATE_NORMAL, 1, 1, 1)
end
end
local name = animal:getName()
local identifier = animal:getIdentifiers()
if name == "" then
cell:getAttribute("idNoName"):setText(identifier)
else
cell:getAttribute("name"):setText(name)
cell:getAttribute("id"):setText(identifier)
end
cell:getAttribute("id"):setVisible(name ~= "")
cell:getAttribute("name"):setVisible(name ~= "")
cell:getAttribute("idNoName"):setVisible(name == "")
cell:getAttribute("icon"):setImageFilename(item:getFilename())
cell:getAttribute("price"):setValue(item:getPrice())
local descriptor = cell:getAttribute("herdsmanPurchase")
descriptor:setVisible(recentlyBoughtByAI or isMarked)
if recentlyBoughtByAI then
descriptor:setText(g_i18n:getText("rl_ui_herdsmanRecentlyBought"))
elseif isMarked then
local markText = RealisticLivestock.MARKS[animal:getHighestPriorityMark()].text
descriptor:setText(g_i18n:getText("rl_mark_" .. markText))
end
local checkbox = cell:getAttribute("checkbox")
if (self.isInfoMode and not self.isBuyMode) or self.isTrailer then
checkbox:setVisible(false)
else
checkbox:setVisible(true)
local check = cell:getAttribute("check")
if check ~= nil then
local originalIndex = self.filteredItems == nil and index or item.originalIndex
check:setVisible(self.selectedItems[originalIndex] ~= nil and self.selectedItems[originalIndex])
local selectAllText = g_i18n:getText("rl_ui_selectAll")
local selectNoneText = g_i18n:getText("rl_ui_selectNone")
checkbox.onClickCallback = function(animalScreen, button)
if self.selectedItems[originalIndex] then
self.selectedItems[originalIndex] = false
check:setVisible(false)
local hasSelection = false
for _, selected in pairs(self.selectedItems) do
if selected then
hasSelection = true
break
end
end
self.buttonToggleSelectAll:setText(hasSelection and selectNoneText or selectAllText)
else
self.selectedItems[originalIndex] = true
check:setVisible(true)
self.buttonToggleSelectAll:setText(selectNoneText)
end
end
end
end
else
if list == self.targetList then
local item
if filteredItems == nil then
if self.isBuyMode then
item = self.controller:getTargetItems()[index]
else
item = self.controller:getSourceItems(animalType, self.isBuyMode)[index]
end
else
item = filteredItems[index]
end
if item == nil then return end
self.isHorse = g_currentMission.animalSystem:getSubTypeByIndex(item:getSubTypeIndex()).typeIndex == AnimalType.HORSE
local name = item:getName()
if not self.isHorse and not self.isBuyMode and item.cluster ~= nil and item.cluster.uniqueId ~= nil then name = item.cluster.uniqueId .. (name == "" and "" or (" (" .. name .. ")")) end
cell:getAttribute("name"):setText(name)
cell:getAttribute("icon"):setImageFilename(item:getFilename())
cell:getAttribute("separator"):setVisible(index > 1)
cell:getAttribute("amount"):setValue("")
cell:getAttribute("amount"):setText("")
end
return
end
end
AnimalScreen.populateCellForItemInSection = Utils.overwrittenFunction(AnimalScreen.populateCellForItemInSection, RealisticLivestock_AnimalScreen.populateCellForItemInSection)
function AnimalScreen:onClickBuySelected()
local itemsToProcess = {}
local money = 0
local animalTypeIndex = self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()]
for animalIndex, isSelected in pairs(self.selectedItems) do
if isSelected then
if isSelected then
if self.isTrailerFarm then
table.insert(itemsToProcess, animalIndex)
elseif self.isBuyMode then
local animalFound, _, _, totalPrice = self.controller:getSourcePrice(animalTypeIndex, animalIndex, 1)
if animalFound then
table.insert(itemsToProcess, animalIndex)
money = money + totalPrice
end
else
local animalFound, _, _, totalPrice = self.controller:getTargetPrice(animalTypeIndex, animalIndex, 1)
if animalFound then
table.insert(itemsToProcess, animalIndex)
money = money + totalPrice
end
end
end
end
end
self.pendingBulkTransaction = { ["items"] = itemsToProcess, ["animalTypeIndex"] = animalTypeIndex }
local callback, confirmationText, text
if self.isBuyMode then
confirmationText = self.isTrailerFarm and g_i18n:getText("rl_ui_moveConfirmation") or g_i18n:getText("rl_ui_buyConfirmation")
callback = self.buySelected
text = self.controller:getSourceActionText()
else
confirmationText = self.isTrailerFarm and g_i18n:getText("rl_ui_moveConfirmation") or g_i18n:getText("rl_ui_sellConfirmation")
callback = self.sellSelected
text = self.controller:getTargetActionText()
end
YesNoDialog.show(callback, self, string.format(confirmationText, #itemsToProcess, g_i18n:formatMoney(money, 2, true, true)), g_i18n:getText("ui_attention"), text, g_i18n:getText("button_back"))
end
function AnimalScreen:buySelected(clickYes)
if not clickYes or self.pendingBulkTransaction == nil then return end
self.controller:applySourceBulk(self.pendingBulkTransaction.animalTypeIndex, self.pendingBulkTransaction.items)
self.selectedItems = {}
end
function AnimalScreen:sellSelected(clickYes)
if not clickYes or self.pendingBulkTransaction == nil then return end
self.controller:applyTargetBulk(self.pendingBulkTransaction.animalTypeIndex, self.pendingBulkTransaction.items)
self.selectedItems = {}
end
function AnimalScreen:onClickFilter()
local animalTypeIndex = self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()]
AnimalFilterDialog.show(self.isBuyMode and self.controller:getSourceItems(animalTypeIndex, self.isBuyMode) or self.controller:getTargetItems(), animalTypeIndex, self.onApplyFilters, self, self.isBuyMode)
end
function AnimalScreen:onApplyFilters(filters, filteredItems)
self.filters = filters
self.filteredItems = filteredItems
self.selectedItems = {}
self.buttonToggleSelectAll:setText(g_i18n:getText("rl_ui_selectAll"))
self.sourceList:reloadData(true)
end
function RealisticLivestock_AnimalScreen:getPrice()
local animalIndex
local animalTypeIndex = self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()]
if self.filteredItems == nil then
animalIndex = self.sourceList.selectedIndex
elseif #self.filteredItems > 0 and self.filteredItems[self.sourceList.selectedIndex] ~= nil then
animalIndex = self.filteredItems[self.sourceList.selectedIndex].originalIndex
else
return false, 0, 0, 0
end
local isFound, price, transportationFee, totalPrice = false, 0, 0, 0
if self.isBuyMode then
isFound, price, transportationFee, totalPrice = self.controller:getSourcePrice(animalTypeIndex, animalIndex, 1)
else
isFound, price, transportationFee, totalPrice = self.controller:getTargetPrice(animalTypeIndex, animalIndex, 1)
end
return isFound, price, transportationFee, totalPrice
end
AnimalScreen.getPrice = Utils.overwrittenFunction(AnimalScreen.getPrice, RealisticLivestock_AnimalScreen.getPrice)
function RealisticLivestock_AnimalScreen:onClickBuy()
self.numAnimals = 1
local animalIndex
if self.filteredItems == nil then
animalIndex = self.sourceList.selectedIndex
else
animalIndex = self.filteredItems[self.sourceList.selectedIndex].originalIndex
end
local confirmationText = self.controller:getApplySourceConfirmationText(self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()], animalIndex, 1)
local actionText = self.controller:getSourceActionText()
YesNoDialog.show(self.onYesNoSource, self, confirmationText, g_i18n:getText("ui_attention"), actionText, g_i18n:getText("button_back"))
return true
end
AnimalScreen.onClickBuy = Utils.overwrittenFunction(AnimalScreen.onClickBuy, RealisticLivestock_AnimalScreen.onClickBuy)
function RealisticLivestock_AnimalScreen:onClickSell()
self.numAnimals = 1
local animalIndex
if self.filteredItems == nil then
animalIndex = self.sourceList.selectedIndex
else
animalIndex = self.filteredItems[self.sourceList.selectedIndex].originalIndex
end
local confirmationText = self.controller:getApplyTargetConfirmationText(self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()], animalIndex, 1)
local actionText = self.controller:getTargetActionText()
YesNoDialog.show(self.onYesNoTarget, self, confirmationText, g_i18n:getText("ui_attention"), actionText, g_i18n:getText("button_back"))
return true
end
AnimalScreen.onClickSell = Utils.overwrittenFunction(AnimalScreen.onClickSell, RealisticLivestock_AnimalScreen.onClickSell)
function RealisticLivestock_AnimalScreen:onYesNoSource(_, clickYes)
if clickYes then
local animalIndex
if self.filteredItems == nil then
animalIndex = self.sourceList.selectedIndex
else
animalIndex = self.filteredItems[self.sourceList.selectedIndex].originalIndex
end
self.controller:applySource(self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()], animalIndex, 1)
end
end
AnimalScreen.onYesNoSource = Utils.overwrittenFunction(AnimalScreen.onYesNoSource, RealisticLivestock_AnimalScreen.onYesNoSource)
function RealisticLivestock_AnimalScreen:onYesNoTarget(_, clickYes)
if clickYes then
local animalIndex
if self.filteredItems == nil then
animalIndex = self.sourceList.selectedIndex
else
animalIndex = self.filteredItems[self.sourceList.selectedIndex].originalIndex
end
self.controller:applyTarget(self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()], animalIndex, 1)
end
end
AnimalScreen.onYesNoTarget = Utils.overwrittenFunction(AnimalScreen.onYesNoTarget, RealisticLivestock_AnimalScreen.onYesNoTarget)
function RealisticLivestock_AnimalScreen:onSourceActionFinished(_, error, text)
local dialogType = error and DialogElement.TYPE_WARNING or DialogElement.TYPE_INFO
if self.filteredItems ~= nil then
local item = self.filteredItems[self.sourceList.selectedIndex]
if item ~= nil then table.remove(self.filteredItems, self.sourceList.selectedIndex) end
end
InfoDialog.show(text, self.updateScreen, self, dialogType, nil, nil, true)
end
AnimalScreen.onSourceActionFinished = Utils.overwrittenFunction(AnimalScreen.onSourceActionFinished, RealisticLivestock_AnimalScreen.onSourceActionFinished)
function RealisticLivestock_AnimalScreen:onTargetActionFinished(_, error, text)
local dialogType = error and DialogElement.TYPE_WARNING or DialogElement.TYPE_INFO
if self.filteredItems ~= nil then
local item = self.filteredItems[self.sourceList.selectedIndex]
if item ~= nil then table.remove(self.filteredItems, self.sourceList.selectedIndex) end
end
InfoDialog.show(text, self.updateScreen, self, dialogType, nil, nil, true)
end
AnimalScreen.onTargetActionFinished = Utils.overwrittenFunction(AnimalScreen.onTargetActionFinished, RealisticLivestock_AnimalScreen.onTargetActionFinished)
function AnimalScreen:onSourceBulkActionFinished(error, text, indexes)
local dialogType = error and DialogElement.TYPE_WARNING or DialogElement.TYPE_INFO
if self.filteredItems ~= nil then
for _, index in pairs(indexes) do
for i, item in pairs(self.filteredItems) do
if item.originalIndex == index then
table.remove(self.filteredItems, i)
break
end
end
end
end
InfoDialog.show(text, self.updateScreen, self, dialogType, nil, nil, true)
end
function AnimalScreen:onTargetBulkActionFinished(error, text, indexes)
local dialogType = error and DialogElement.TYPE_WARNING or DialogElement.TYPE_INFO
if self.filteredItems ~= nil then
for _, index in pairs(indexes) do
for i, item in pairs(self.filteredItems) do
if item.originalIndex == index then
table.remove(self.filteredItems, i)
break
end
end
end
end
InfoDialog.show(text, self.updateScreen, self, dialogType, nil, nil, true)
end
function AnimalScreen:onClickToggleSelectAll()
local selectAll = true
for _, selected in pairs(self.selectedItems) do
if selected then
selectAll = false
break
end
end
local animalType = self.sourceSelectorStateToAnimalType[self.sourceSelector:getState()]
local items
if self.filteredItems == nil then
if self.isBuyMode then
items = self.controller:getSourceItems(animalType, self.isBuyMode)
else
items = self.controller:getTargetItems()
end
else
items = self.filteredItems
end
for i, item in pairs(items) do
self.selectedItems[self.filteredItems == nil and i or item.originalIndex] = selectAll
end
self.buttonToggleSelectAll:setText(selectAll and g_i18n:getText("rl_ui_selectNone") or g_i18n:getText("rl_ui_selectAll"))
self.sourceList:reloadData()
end
function RealisticLivestock_AnimalScreen:setSelectionState(superFunc, state) -- ?
local returnValue = superFunc(self, state)
local hasItems = self.sourceList:getItemCount() > 0
self.buttonBuy:setVisible(self.isBuyMode and hasItems)
self.buttonSell:setVisible(not self.isBuyMode and not self.isInfoMode and hasItems)
self.buttonsPanel:invalidateLayout()
return returnValue
end
AnimalScreen.setSelectionState = Utils.overwrittenFunction(AnimalScreen.setSelectionState, RealisticLivestock_AnimalScreen.setSelectionState)
function AnimalScreen:onClickInfoPrompt() end
function AnimalScreen:onHighlightInfoPrompt(button)
self.infoPrompt:setVisible(true)
local x = button.absPosition[1] - self.infoPrompt.size[1]
local y = button.absPosition[2] - self.infoPrompt.size[2] * 0.5
self.infoPrompt:setAbsolutePosition(x, y)
end
function AnimalScreen:onHighlightRemoveInfoPrompt()
self.infoPrompt:setVisible(false)
end
================================================
FILE: src/gui/RealisticLivestock_InGameMenuAnimalsFrame.lua
================================================
RealisticLivestock_InGameMenuAnimalsFrame = {}
function RealisticLivestock_InGameMenuAnimalsFrame:displayCluster(superFunc, animal, husbandry)
if g_currentMission.isRunning or Platform.isMobile then
local animalSystem = g_currentMission.animalSystem
local subTypeIndex = animal:getSubTypeIndex()
local age = animal:getAge()
local visual = animalSystem:getVisualByAge(subTypeIndex, age)
if visual ~= nil then
local subType = animal:getSubType()
--local fillTypeTitle = g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex)
local name = animal:getName()
name = name ~= "" and (" (" .. name .. ")") or ""
self.animalDetailTypeNameText:setText(animal.uniqueId .. name)
self.animalDetailTypeImage:setImageFilename(visual.store.imageFilename)
local ageMonth = g_i18n:formatNumMonth(age)
self.animalAgeText:setText(ageMonth)
local animalInfo = husbandry:getAnimalInfos(animal)
for a, b in ipairs(self.infoRow) do
local row = animalInfo[a]
b:setVisible(row ~= nil)
if row ~= nil then
local valueText = row.valueText or g_i18n:formatVolume(row.value, 0, row.customUnitText)
self.infoLabel[a]:setText(row.title)
self.infoValue[a]:setText(valueText)
self:setStatusBarValue(self.infoStatusBar[a], row.ratio, row.invertedBar, row.disabled)
end
end
local description = husbandry:getAnimalDescription(animal)
self.detailDescriptionText:setText(description)
end
end
end
InGameMenuAnimalsFrame.displayCluster = Utils.overwrittenFunction(InGameMenuAnimalsFrame.displayCluster, RealisticLivestock_InGameMenuAnimalsFrame.displayCluster)
function RealisticLivestock_InGameMenuAnimalsFrame:populateCellForItemInSection(_, subTypeIndex, animalIndex, cell)
local subType = self.husbandrySubTypes[subTypeIndex]
local animal = self.subTypeIndexToClusters[subType][animalIndex]
if g_currentMission.animalSystem:getVisualByAge(subType, animal:getAge()) ~= nil then
cell:getAttribute("name"):setText(animal.uniqueId .. (animal:getName() == "" and "" or (" (" .. animal:getName() .. ")")))
cell:getAttribute("count"):setVisible(false)
end
end
InGameMenuAnimalsFrame.populateCellForItemInSection = Utils.appendedFunction(InGameMenuAnimalsFrame.populateCellForItemInSection, RealisticLivestock_InGameMenuAnimalsFrame.populateCellForItemInSection)
================================================
FILE: src/gui/VisualAnimalsDialog.lua
================================================
VisualAnimalsDialog = {}
local visualAnimalsDialog_mt = Class(VisualAnimalsDialog, YesNoDialog)
local modDirectory = g_currentModDirectory
function VisualAnimalsDialog.register()
local dialog = VisualAnimalsDialog.new()
g_gui:loadGui(modDirectory .. "gui/VisualAnimalsDialog.xml", "VisualAnimalsDialog", dialog)
VisualAnimalsDialog.INSTANCE = dialog
end
function VisualAnimalsDialog.show()
if VisualAnimalsDialog.INSTANCE == nil then VisualAnimalsDialog.register() end
if VisualAnimalsDialog.INSTANCE ~= nil then
local instance = VisualAnimalsDialog.INSTANCE
local profile = Utils.getPerformanceClassId()
local recommendedAnimals = (profile == GS_PROFILE_VERY_LOW and 8) or (profile == GS_PROFILE_LOW and 10) or (profile == GS_PROFILE_MEDIUM and 16) or (profile == GS_PROFILE_HIGH and 20) or (profile == GS_PROFILE_VERY_HIGH and 25) or (profile == GS_PROFILE_ULTRA and 25) or 8
local maxHusbandries = RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES
--local currentMaxAnimals = recommendedAnimals * maxHusbandries
local currentMaxAnimals = 1 * maxHusbandries
local maxAnimals = recommendedAnimals * 8
instance.recommendedAnimals = recommendedAnimals
instance:setQuantity(maxAnimals)
instance.quantityElement:setState(maxHusbandries)
g_gui:showDialog("VisualAnimalsDialog")
end
end
function VisualAnimalsDialog.new(target, customMt)
local dialog = YesNoDialog.new(target, customMt or visualAnimalsDialog_mt)
dialog.areButtonsDisabled = false
dialog.recommendedAnimals = 8
return dialog
end
function VisualAnimalsDialog.createFromExistingGui(gui, _)
VisualAnimalsDialog.register()
VisualAnimalsDialog.show()
end
function VisualAnimalsDialog:onOpen()
VisualAnimalsDialog:superClass().onOpen(self)
FocusManager:setFocus(self.itemsElement)
end
function VisualAnimalsDialog:onClose()
VisualAnimalsDialog:superClass().onClose(self)
end
function VisualAnimalsDialog:onRecommended()
if self.areButtonsDisabled then return true end
self.quantityElement:setState(self.recommendedAnimals * 2)
return false
end
function VisualAnimalsDialog:onYes()
if self.areButtonsDisabled then return true end
local maxHusbandries = RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES
local newMaxHusbandries = self.quantityElement:getState()
local husbandrySystem = g_currentMission.husbandrySystem
if maxHusbandries ~= newMaxHusbandries then
RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES = newMaxHusbandries
for _, clusterHusbandry in ipairs(husbandrySystem.clusterHusbandries) do
clusterHusbandry.nextUpdateClusters = clusterHusbandry.placeable.spec_husbandryAnimals.clusterSystem:getAnimals()
clusterHusbandry:updateVisuals(maxHusbandries > newMaxHusbandries)
end
end
self:close()
return false
end
function VisualAnimalsDialog:onNo(_, _)
self:close()
return false
end
function VisualAnimalsDialog:setQuantity(quantity)
if quantity < 1 then quantity = 1 end
self.maxQuantity = quantity
local texts = {}
for i=1, quantity do
local text = tostring(i)
table.insert(texts, text)
end
self.quantityElement:setTexts(texts)
end
function VisualAnimalsDialog:setButtonDisabled(disabled)
self.areButtonsDisabled = disabled
self.yesButton:setDisabled(disabled)
end
================================================
FILE: src/gui/elements/DoubleOptionSliderElement.lua
================================================
DoubleOptionSliderElement = {}
local doubleOptionSliderElement_mt = Class(DoubleOptionSliderElement, MultiTextOptionElement)
Gui.registerGuiElement("DoubleOptionSlider", DoubleOptionSliderElement)
Gui.registerGuiElementProcFunction("DoubleOptionSlider", Gui.assignPlaySampleCallback)
function DoubleOptionSliderElement.new(target, customMt)
local self = MultiTextOptionElement.new(target, customMt or doubleOptionSliderElement_mt)
self.leftSliderElement = nil
self.rightSliderElement = nil
self.sliderOffset = nil
self.defaultProfileSlider = nil
self.defaultProfileSliderRound = nil
self.useFillingBar = false
self.fillingBarElement = nil
self.defaultProfileFillingBar = nil
self.defaultProfileFillingBarThreePart = nil
self.updateTextPosition = true
self.leftState = 1
self.rightState = 2
self.fillingBarOffset = GuiUtils.getNormalizedScreenValues("2px 0px")
return self
end
function DoubleOptionSliderElement:loadFromXML(handle, key)
DoubleOptionSliderElement:superClass().loadFromXML(self, handle, key)
self.sliderOffset = GuiUtils.getNormalizedXValue(getXMLInt(handle, key .. "#sliderOffset"), self.sliderOffset)
self.useFillingBar = getXMLBool(handle, key .. "#useFillingBar") or self.useFillingBar
self.updateTextPosition = getXMLBool(handle, key .. "#updateTextPosition") or self.updateTextPosition
end
function DoubleOptionSliderElement:loadProfile(profile, applyProfile)
DoubleOptionSliderElement:superClass().loadProfile(self, profile, applyProfile)
self.sliderOffset = GuiUtils.getNormalizedXValue(profile:getValue("sliderOffset"), self.sliderOffset)
self.useFillingBar = profile:getBool("useFillingBar", self.useFillingBar)
self.updateTextPosition = profile:getBool("updateTextPosition", self.updateTextPosition)
self.defaultProfileSlider = profile:getValue("defaultProfileSlider", self.defaultProfileSlider)
self.defaultProfileSliderRound = profile:getValue("defaultProfileSliderRound", self.defaultProfileSliderRound)
self.defaultProfileFillingBar = profile:getValue("defaultProfileFillingBar", self.defaultProfileFillingBar)
self.defaultProfileFillingBarThreePart = profile:getValue("defaultProfileFillingBarThreePart", self.defaultProfileFillingBarThreePart)
end
function DoubleOptionSliderElement:copyAttributes(target)
self.sliderOffset = target.sliderOffset
self.useFillingBar = target.useFillingBar
self.updateTextPosition = target.updateTextPosition
self.defaultProfileSlider = target.defaultProfileSlider
self.defaultProfileSliderRound = target.defaultProfileSliderRound
self.defaultProfileFillingBar = target.defaultProfileFillingBar
self.defaultProfileFillingBarThreePart = target.defaultProfileFillingBarThreePart
self.leftSliderElement = target.leftSliderElement
self.rightSliderElement = target.rightSliderElement
self.leftState = target.leftState
self.rightState = target.rightState
self.leftSliderMousePosX = target.leftSliderMousePosX
self.rightSliderMousePosX = target.rightSliderMousePosX
self.texts = target.texts
self.isLeftSliderPressed = target.isLeftSliderPressed
self.isRightSliderPressed = target.isRightSliderPressed
DoubleOptionSliderElement:superClass().copyAttributes(self, target)
end
function DoubleOptionSliderElement:setElementsByName()
DoubleOptionSliderElement:superClass().setElementsByName(self)
for _, element in pairs(self.elements) do
if element.name == "leftSlider" then
self.leftSliderElement = element
element.target = self
end
if element.name == "rightSlider" then
self.rightSliderElement = element
element.target = self
end
if element.name == "fillingBar" then
self.fillingBarElement = element
element.target = self
end
end
if self.fillingBarElement == nil then
self.useFillingBar = false
end
if self.leftSliderElement == nil then
Logging.warning("DoubleOptionSliderElement: could not find a left slider element for element with profile " .. self.profile)
elseif self.rightSliderElement == nil then
Logging.warning("DoubleOptionSliderElement: could not find a right slider element for element with profile " .. self.profile)
end
end
function DoubleOptionSliderElement:addDefaultElements()
DoubleOptionSliderElement:superClass().addDefaultElements(self)
if self.autoAddDefaultElements then
if self:getDescendantByName("fillingBar") == nil then
if self.defaultProfileFillingBar == nil then
if self.defaultProfileFillingBarThreePart ~= nil then
local element = ThreePartBitmapElement.new(self)
element.name = "fillingBar"
self:addElement(element)
element:applyProfile(self.defaultProfileFillingBarThreePart)
end
else
local element = BitmapElement.new(self)
element.name = "fillingBar"
self:addElement(element)
element:applyProfile(self.defaultProfileFillingBar)
end
end
if self:getDescendantByName("leftSlider") == nil then
if self.defaultProfileSliderRound ~= nil then
local element = RoundCornerElement.new(self)
element.name = "leftSlider"
self:addElement(element)
element:applyProfile(self.defaultProfileSliderRound)
return
end
if self.defaultProfileSlider ~= nil then
local element = BitmapElement.new(self)
element.name = "leftSlider"
self:addElement(element)
element:applyProfile(self.defaultProfileSlider)
end
end
if self:getDescendantByName("rightSlider") == nil then
if self.defaultProfileSliderRound ~= nil then
local element = RoundCornerElement.new(self)
element.name = "rightSlider"
self:addElement(element)
element:applyProfile(self.defaultProfileSliderRound)
return
end
if self.defaultProfileSlider ~= nil then
local element = BitmapElement.new(self)
element.name = "rightSlider"
self:addElement(element)
element:applyProfile(self.defaultProfileSlider)
end
end
end
end
function DoubleOptionSliderElement:onOpen()
DoubleOptionSliderElement:superClass().onOpen(self)
self:updateSlider()
end
function DoubleOptionSliderElement:mouseEvent(posX, posY, isDown, isUp, button, eventUsed)
if self:getIsActive() then
eventUsed = self.wasContinuousTrigger and isUp and true or (MultiTextOptionElement:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) and true or eventUsed)
if isDown then
local leftButton = self.leftButtonElement
self.isLeftButtonPressed = not self.hideLeftRightButtons and GuiUtils.checkOverlayOverlap(posX, posY, leftButton.absPosition[1], leftButton.absPosition[2], leftButton.absSize[1], leftButton.absSize[2], leftButton.hotspot)
local rightButton = self.rightButtonElement
self.isRightButtonPressed = not self.hideLeftRightButtons and GuiUtils.checkOverlayOverlap(posX, posY, rightButton.absPosition[1], rightButton.absPosition[2], rightButton.absSize[1], rightButton.absSize[2], rightButton.hotspot)
local leftSlider = self.leftSliderElement
self.isLeftSliderPressed = leftSlider ~= nil and GuiUtils.checkOverlayOverlap(posX, posY, leftSlider.absPosition[1], leftSlider.absPosition[2], leftSlider.absSize[1], leftSlider.absSize[2], leftSlider.hotspot)
local rightSlider = self.rightSliderElement
self.isRightSliderPressed = rightSlider ~= nil and GuiUtils.checkOverlayOverlap(posX, posY, rightSlider.absPosition[1], rightSlider.absPosition[2], rightSlider.absSize[1], rightSlider.absSize[2], rightSlider.hotspot)
if self.leftSliderMousePosX == nil and self.isLeftSliderPressed then self.leftSliderMousePosX = posX end
if self.rightSliderMousePosX == nil and self.isRightSliderPressed then self.rightSliderMousePosX = posX end
self.delayTime = g_time
elseif isUp then
self.delayTime = math.huge
self.scrollDelayDuration = MultiTextOptionElement.FIRST_INPUT_DELAY
self.wasContinuousTrigger = false
self.continuousTriggerTime = 0
self.isLeftButtonPressed = false
self.leftDelayTime = 0
self.isRightButtonPressed = false
self.rightDelayTime = 0
self.isLeftSliderPressed = false
self.isRightSliderPressed = false
self.leftSliderMousePosX = nil
self.rightSliderMousePosX = nil
self.hasWrapped = false
end
if eventUsed or not GuiUtils.checkOverlayOverlap(posX, posY, self.absPosition[1], self.absPosition[2], self.absSize[1], self.absSize[2], nil) then
if self.inputEntered and not self.focusActive then
FocusManager:unsetHighlight(self)
self.inputEntered = false
end
else
if not (self.inputEntered or self:getIsFocused()) then
FocusManager:setHighlight(self)
self.inputEntered = true
end
if #self.texts > 1 then
if not self:getIsFocused() and (self.isLeftSliderPressed or self.isRightSliderPressed) then FocusManager:setFocus(self) end
if self.isLeftSliderPressed then
local slider = self.leftSliderElement
local sliderWidth = slider.absSize[1]
local stepSize = (self.absSize[1] - 2 * self.sliderOffset - sliderWidth) / (#self.texts - 1)
local mouseMoveDistance = posX - self.leftSliderMousePosX
local sliderLocalPosX = slider.absPosition[1] - self.absPosition[1] - self.sliderOffset
local sliderPosX = MathUtil.snapValue(sliderLocalPosX + mouseMoveDistance, stepSize)
sliderPosX = math.clamp(sliderPosX, 0, self.absSize[1] - sliderWidth - 2 * self.sliderOffset)
local state = MathUtil.round(sliderPosX / stepSize) + 1
--if state ~= self.leftState and (self.leftState ~= self.rightState or (self.leftState == self.rightState and state < self.leftState)) then
if state ~= self.leftState then
--print(string.format("left: %s, %s, %s", tostring(state ~= self.leftState), tostring(self.leftState == self.rightState), tostring(state < self.leftState)))
self.leftSliderMousePosX = self.leftSliderMousePosX + stepSize * (state - self.leftState)
self.leftState = state
self:setState(state, true)
slider:setAbsolutePosition(self.absPosition[1] + sliderPosX + self.sliderOffset, slider.absPosition[2])
if self.updateTextPosition then self.textElement:setAbsolutePosition(slider.absPosition[1] - (self.textElement.absSize[1] - slider.absSize[1]) * 0.5, self.textElement.absPosition[2]) end
if self.useFillingBar then
local lowestSlider = self:getLowestSlider()
self.fillingBarElement.offset[1] = lowestSlider.absPosition[1] - self.fillingBarElement.absPosition[1] + self.fillingBarOffset[1]
self.fillingBarElement:setSize(math.abs((self.leftState - 1) - (self.rightState - 1)) / (#self.texts - 1) * (self.absSize[1] - self.sliderOffset * 2) + self.sliderOffset, nil)
end
end
elseif self.isRightSliderPressed then
local slider = self.rightSliderElement
local sliderWidth = slider.absSize[1]
local stepSize = (self.absSize[1] - 2 * self.sliderOffset - sliderWidth) / (#self.texts - 1)
local mouseMoveDistance = posX - self.rightSliderMousePosX
local sliderLocalPosX = slider.absPosition[1] - self.absPosition[1] - self.sliderOffset
local sliderPosX = MathUtil.snapValue(sliderLocalPosX + mouseMoveDistance, stepSize)
sliderPosX = math.clamp(sliderPosX, 0, self.absSize[1] - sliderWidth - 2 * self.sliderOffset)
local state = MathUtil.round(sliderPosX / stepSize) + 1
--if state ~= self.rightState and (self.leftState ~= self.rightState or (self.leftState == self.rightState and state > self.rightState)) then
if state ~= self.rightState then
--print(string.format("right: %s, %s, %s", tostring(state ~= self.rightState), tostring(self.leftState == self.rightState), tostring(state > self.rightState)))
self.rightSliderMousePosX = self.rightSliderMousePosX + stepSize * (state - self.rightState)
self.rightState = state
self:setState(state, true)
slider:setAbsolutePosition(self.absPosition[1] + sliderPosX + self.sliderOffset, slider.absPosition[2])
if self.updateTextPosition then self.textElement:setAbsolutePosition(slider.absPosition[1] - (self.textElement.absSize[1] - slider.absSize[1]) * 0.5, self.textElement.absPosition[2]) end
if self.useFillingBar then
local lowestSlider = self:getLowestSlider()
self.fillingBarElement.offset[1] = lowestSlider.absPosition[1] - self.fillingBarElement.absPosition[1] + self.fillingBarOffset[1]
self.fillingBarElement:setSize(math.abs((self.leftState - 1) - (self.rightState - 1)) / (#self.texts - 1) * (self.absSize[1] - self.sliderOffset * 2) + self.sliderOffset, nil)
end
end
end
end
end
end
return eventUsed
end
function DoubleOptionSliderElement:updateSlider()
if self.leftSliderElement ~= nil then
if self.sliderOffset == nil then
self.sliderOffset = self.leftButtonElement.absSize[1]
end
local text = self.textElement
local slider = self.leftSliderElement
local minVal = self.absPosition[1] + self.sliderOffset
local maxVal = self.absPosition[1] + self.absSize[1] - slider.absSize[1] - self.sliderOffset
local pos = maxVal
if #self.texts > 1 then
pos = minVal + (self.leftState - 1) / (#self.texts - 1) * (maxVal - minVal)
end
slider:setAbsolutePosition(pos, slider.absPosition[2])
if self.updateTextPosition then
text:setAbsolutePosition(pos - (text.absSize[1] - slider.absSize[1]) * 0.5, text.absPosition[2])
end
end
if self.rightSliderElement ~= nil then
if self.sliderOffset == nil then
self.sliderOffset = self.leftButtonElement.absSize[1]
end
local text = self.textElement
local slider = self.rightSliderElement
local minVal = self.absPosition[1] + self.sliderOffset
local maxVal = self.absPosition[1] + self.absSize[1] - slider.absSize[1] - self.sliderOffset
local pos = maxVal
if #self.texts > 1 then
pos = minVal + (self.rightState - 1) / (#self.texts - 1) * (maxVal - minVal)
end
slider:setAbsolutePosition(pos, slider.absPosition[2])
end
if self.useFillingBar and self.leftSliderElement ~= nil and self.rightSliderElement ~= nil then
local fillingBarSize = self.absSize[1] - self.sliderOffset
if #self.texts > 1 then
fillingBarSize = math.abs((self.leftState - 1) - (self.rightState - 1)) / (#self.texts - 1) * (self.absSize[1] - self.sliderOffset * 2) + self.sliderOffset
end
local lowestSlider = self:getLowestSlider()
self.fillingBarElement.offset[1] = lowestSlider.absPosition[1] - self.fillingBarElement.absPosition[1] + self.fillingBarOffset[1]
self.fillingBarElement:setSize(fillingBarSize, nil)
end
end
function DoubleOptionSliderElement:updateFillingBar()
if self.useFillingBar and self.leftSliderElement ~= nil and self.rightSliderElement ~= nil then
local fillingBarSize = self.absSize[1] - self.sliderOffset
if #self.texts > 1 then
fillingBarSize = math.abs((self.leftState - 1) - (self.rightState - 1)) / (#self.texts - 1) * (self.absSize[1] - self.sliderOffset * 2) + self.sliderOffset
end
local lowestSlider = self:getLowestSlider()
self.fillingBarElement.offset[1] = lowestSlider.absPosition[1] - self.fillingBarElement.absPosition[1] + self.fillingBarOffset[1]
self.fillingBarElement:setSize(fillingBarSize, nil)
end
end
function DoubleOptionSliderElement:updateAbsolutePosition()
DoubleOptionSliderElement:superClass().updateAbsolutePosition(self)
self:updateSlider()
end
function DoubleOptionSliderElement:updateContentElement()
DoubleOptionSliderElement:superClass().updateContentElement(self)
if self.texts ~= nil and #self.texts > 0 then
local lowestState = self:getLowestState()
local highestState = self:getHighestState()
if lowestState == highestState then
self.textElement:setText(self.texts[lowestState])
else
self.textElement:setText(self.texts[lowestState] .. " - " .. self.texts[highestState])
end
end
end
function DoubleOptionSliderElement:setTexts(texts)
self.leftState = 1
self.rightState = #texts
DoubleOptionSliderElement:superClass().setTexts(self, texts)
self:updateSlider()
end
function DoubleOptionSliderElement:getHighestSlider()
return self.leftState > self.rightState and self.leftSliderElement or self.rightSliderElement
end
function DoubleOptionSliderElement:getLowestSlider()
return self.leftState <= self.rightState and self.leftSliderElement or self.rightSliderElement
end
function DoubleOptionSliderElement:getHighestState()
return self.leftState > self.rightState and self.leftState or self.rightState
end
function DoubleOptionSliderElement:getLowestState()
return self.leftState <= self.rightState and self.leftState or self.rightState
end
================================================
FILE: src/gui/elements/RenderElement.lua
================================================
RL_RenderElement = {}
local modDirectory = g_currentModDirectory
function RL_RenderElement:setScene(superFunc, filename)
if filename == "animals/domesticated/earTagScene.i3d" then
self.isRealisticLivestockAsset = true
filename = modDirectory .. filename
end
superFunc(self, filename)
end
RenderElement.setScene = Utils.overwrittenFunction(RenderElement.setScene, RL_RenderElement.setScene)
function RL_RenderElement:onSceneLoaded(node, failedReason, _)
if failedReason == LoadI3DFailedReason.NONE and self.isRealisticLivestockAsset then setVisibility(node, true) end
end
RenderElement.onSceneLoaded = Utils.appendedFunction(RenderElement.onSceneLoaded, RL_RenderElement.onSceneLoaded)
================================================
FILE: src/gui/elements/TripleOptionElement.lua
================================================
TripleOptionElement = {}
TripleOptionElement.STATE_LEFT = 1
TripleOptionElement.STATE_MIDDLE = 2
TripleOptionElement.STATE_RIGHT = 3
TripleOptionElement.STRING_ON = "ui_on"
TripleOptionElement.STRING_ANY = "Any"
TripleOptionElement.STRING_OFF = "ui_off"
TripleOptionElement.STRING_YES = "ui_yes"
TripleOptionElement.STRING_NO = "ui_no"
TripleOptionElement.NUM_SLIDER_STATES = 12
local tripleOptionElement_mt = Class(TripleOptionElement, MultiTextOptionElement)
Gui.registerGuiElement("TripleOption", TripleOptionElement)
Gui.registerGuiElementProcFunction("TripleOption", Gui.assignPlaySampleCallback)
function TripleOptionElement.new(target, custom_mt)
local self = MultiTextOptionElement.new(target, custom_mt or tripleOptionElement_mt)
self.sliderElement = nil
self.isSliderMoving = false
self.sliderState = 0
self.sliderMovingDirection = 0
self.middleButtonElement = nil
self.defaultProfileButtonMiddle = nil
self.useYesNoTexts = false
return self
end
function TripleOptionElement:loadFromXML(xmlFile, key)
TripleOptionElement:superClass().loadFromXML(self, xmlFile, key)
self.useYesNoTexts = Utils.getNoNil(getXMLBool(xmlFile, key.."#useYesNoTexts"), self.useYesNoTexts)
end
function TripleOptionElement:loadProfile(profile, applyProfile)
TripleOptionElement:superClass().loadProfile(self, profile, applyProfile)
self.useYesNoTexts = profile:getBool("useYesNoTexts", self.useYesNoTexts)
self.sliderOffset = GuiUtils.getNormalizedScreenValues(profile:getValue("sliderOffset"), self.sliderOffset)
self.defaultProfileSlider = profile:getValue("defaultProfileSlider", self.defaultProfileSlider)
self.defaultProfileSliderRound = profile:getValue("defaultProfileSliderRound", self.defaultProfileSliderRound)
self.defaultProfileSliderThreePart = profile:getValue("defaultProfileSliderThreePart", self.defaultProfileSliderThreePart)
self.defaultProfileButtonMiddle = profile:getValue("defaultProfileButtonMiddle", self.defaultProfileButtonMiddle)
end
function TripleOptionElement:copyAttributes(src)
TripleOptionElement:superClass().copyAttributes(self, src)
self.useYesNoTexts = src.useYesNoTexts
self.defaultProfileSlider = src.defaultProfileSlider
self.defaultProfileSliderRound = src.defaultProfileSliderRound
self.defaultProfileSliderThreePart = src.defaultProfileSliderThreePart
self.defaultProfileButtonMiddle = src.defaultProfileButtonMiddle
end
function TripleOptionElement:setElementsByName()
TripleOptionElement:superClass().setElementsByName(self)
for _, element in pairs(self.elements) do
if element.name == "slider" then
self.sliderElement = element
element.target = self
element:updateAbsolutePosition()
end
if element.name == "middle" then
if self.middleButtonElement ~= nil and self.middleButtonElement ~= element then self.middleButtonElement:delete() end
self.middleButtonElement = element
element.target = self
element:setHandleFocus(false)
element:setCallback("onClickCallback", "onMiddleButtonClicked")
element:setDisabled(self.disabled)
element:setVisible(not self.hideLeftRightButtons)
end
end
if self.sliderElement == nil then
Logging.warning("TripleOptionElement: could not find a slider element for element with profile " .. self.profile)
end
self.leftButtonElement:setSelected(true)
self.leftButtonElement.getIsSelected = function() return self.state == TripleOptionElement.STATE_LEFT end
self.leftButtonElement.getIsScrollingAllowed = function() return self:getIsFocused() or self:getIsHighlighted() end
self.middleButtonElement.getIsSelected = function() return self.state == TripleOptionElement.STATE_MIDDLE end
self.middleButtonElement.getIsScrollingAllowed = function() return self:getIsFocused() or self:getIsHighlighted() end
self.rightButtonElement.getIsSelected = function() return self.state == TripleOptionElement.STATE_RIGHT end
self.rightButtonElement.getIsScrollingAllowed = function() return self:getIsFocused() or self:getIsHighlighted() end
self.sliderDelta = (self.absSize[1] - self.sliderElement.absSize[1]) / TripleOptionElement.NUM_SLIDER_STATES
end
function TripleOptionElement:addDefaultElements()
TripleOptionElement:superClass().addDefaultElements(self)
if self.autoAddDefaultElements then
if self:getDescendantByName("slider") == nil then
if self.defaultProfileSliderRound ~= nil then
local baseElement = RoundCornerElement.new(self)
baseElement.name = "slider"
self:addElement(baseElement)
baseElement:applyProfile(self.defaultProfileSliderRound)
elseif self.defaultProfileSliderThreePart ~= nil then
local baseElement = ThreePartBitmapElement.new(self)
baseElement.name = "slider"
self:addElement(baseElement)
baseElement:applyProfile(self.defaultProfileSliderThreePart)
elseif self.defaultProfileSlider ~= nil then
local baseElement = BitmapElement.new(self)
baseElement.name = "slider"
self:addElement(baseElement)
baseElement:applyProfile(self.defaultProfileSlider)
end
end
if self:getDescendantByName("middle") == nil then
local element = ButtonElement.new(self)
element.name = "middle"
self:addElement(element)
element:applyProfile(self.defaultProfileButtonMiddle)
end
end
end
function TripleOptionElement:onGuiSetupFinished()
TripleOptionElement:superClass().onGuiSetupFinished(self)
if self.useYesNoTexts then
self:setTexts({g_i18n:getText(TripleOptionElement.STRING_NO), g_i18n:getText(TripleOptionElement.STRING_ANY), g_i18n:getText(TripleOptionElement.STRING_YES)})
else
self:setTexts({g_i18n:getText(TripleOptionElement.STRING_OFF), g_i18n:getText(TripleOptionElement.STRING_ANY), g_i18n:getText(TripleOptionElement.STRING_ON)})
end
self.textElement:setVisible(false)
end
function TripleOptionElement:getIsChecked()
return self.state == TripleOptionElement.STATE_RIGHT
end
function TripleOptionElement:setIsChecked(isChecked, skipAnimation, forceEvent)
if isChecked then
self:setState(TripleOptionElement.STATE_RIGHT, forceEvent)
else
self:setState(TripleOptionElement.STATE_LEFT, forceEvent)
end
self.skipAnimation = skipAnimation
end
function TripleOptionElement:getIsActiveNonRec()
return self:getIsVisibleNonRec()
end
function TripleOptionElement:setTexts(texts)
if #texts ~= 3 then
Logging.warning("TripleOptionElement: called setTexts() with invalid number of texts, triple option requires exactly 3 texts")
printCallstack()
end
TripleOptionElement:superClass().setTexts(self, texts)
self.leftButtonElement:setText(texts[1])
self.middleButtonElement:setText(texts[2])
self.rightButtonElement:setText(texts[3])
end
function TripleOptionElement:update(dt)
TripleOptionElement:superClass().update(self, dt)
if self.sliderMovingDirection ~= 0 then
if self.skipAnimation then
self.sliderState = self.sliderMovingDirection > 0 and TripleOptionElement.NUM_SLIDER_STATES or 0
else
self.sliderState = self.sliderState + self.sliderMovingDirection
end
if self.sliderState <= 0 or self.sliderState >= TripleOptionElement.NUM_SLIDER_STATES or self.sliderState == (self.state - 1) * (TripleOptionElement.NUM_SLIDER_STATES / 2) then
self.sliderMovingDirection = 0
end
self.sliderElement:setPosition(self.sliderDelta * self.sliderState)
end
self.skipAnimation = false
end
function TripleOptionElement:inputLeft()
if self.sliderMovingDirection == 0 and (self:getIsFocused() or self.leftButtonElement:getIsPressed()) then
self:onLeftButtonClicked()
return true
else
return false
end
end
function TripleOptionElement:inputRight()
local middleButtonPressed = self.middleButtonElement:getIsPressed()
local rightButtonPressed = self.rightButtonElement:getIsPressed()
if self.sliderMovingDirection == 0 and (self:getIsFocused() or middleButtonPressed or rightButtonPressed) then
if rightButtonPressed then
self:onRightButtonClicked()
elseif middleButtonPressed then
self:onMiddleButtonClicked()
end
return true
else
return false
end
end
function TripleOptionElement:setState(state, forceEvent, skipAnimation)
if state ~= TripleOptionElement.STATE_LEFT and state ~= TripleOptionElement.STATE_MIDDLE and state ~= TripleOptionElement.STATE_RIGHT then
Logging.warning("TripleOptionElement: invalid state input " .. state .. ", only 1, 2 and 3 allowed")
return
end
if state == self.state then
return
end
self.oldState = self.state
state = math.clamp(state, TripleOptionElement.STATE_LEFT, TripleOptionElement.STATE_RIGHT)
TripleOptionElement:superClass().setState(self, state, forceEvent)
self:updateSelection()
self.skipAnimation = skipAnimation
end
function TripleOptionElement:onRightButtonClicked()
self:setSoundSuppressed(true)
FocusManager:setFocus(self)
self:setSoundSuppressed(false)
if self:getCanChangeState() and self.state ~= TripleOptionElement.STATE_RIGHT then
self:playSample(GuiSoundPlayer.SOUND_SAMPLES.CLICK)
self:setState(TripleOptionElement.STATE_RIGHT)
self:updateContentElement()
self:raiseClickCallback(false)
self:notifyIndexChange(self.state, #self.texts)
end
end
function TripleOptionElement:onMiddleButtonClicked()
self:setSoundSuppressed(true)
FocusManager:setFocus(self)
self:setSoundSuppressed(false)
if self:getCanChangeState() and self.state ~= TripleOptionElement.STATE_MIDDLE then
self:playSample(GuiSoundPlayer.SOUND_SAMPLES.CLICK)
self:setState(TripleOptionElement.STATE_MIDDLE)
self:updateContentElement()
self:raiseClickCallback(false)
self:notifyIndexChange(self.state, #self.texts)
end
end
function TripleOptionElement:onLeftButtonClicked()
self:setSoundSuppressed(true)
FocusManager:setFocus(self)
self:setSoundSuppressed(false)
if self:getCanChangeState() and self.state ~= TripleOptionElement.STATE_LEFT then
self:playSample(GuiSoundPlayer.SOUND_SAMPLES.CLICK)
self:setState(TripleOptionElement.STATE_LEFT)
self:updateContentElement()
self:raiseClickCallback(true)
self:notifyIndexChange(self.state, #self.texts)
end
end
function TripleOptionElement:updateSelection()
self.leftButtonElement:setSelected(self.state == TripleOptionElement.STATE_LEFT)
self.middleButtonElement:setSelected(self.state == TripleOptionElement.STATE_MIDDLE)
self.rightButtonElement:setSelected(self.state == TripleOptionElement.STATE_RIGHT)
self.sliderMovingDirection = self.oldState > self.state and -1 or 1
end
================================================
FILE: src/handTools/HandTool.lua
================================================
RL_HandTool = {}
local modDirectory = g_currentModDirectory
local modName = g_currentModName
function RL_HandTool:load(superFunc, data)
local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName)
if not string.contains(self.type.name, modName) or storeItem ~= nil then return superFunc(self, data) end
self:loadNonStoreItem(data, self.configFileName)
end
HandTool.load = Utils.overwrittenFunction(HandTool.load, RL_HandTool.load)
function HandTool:loadNonStoreItem(data, xmlFile)
self.isRL = true
local handToolSystem = g_currentMission.handToolSystem
self.handToolLoadingData = data
self.savegame = data.savegameData
self.xmlFile = XMLFile.loadIfExists("handTool", xmlFile, HandTool.xmlSchema)
self.configFileName = xmlFile
SpecializationUtil.copyTypeFunctionsInto(self.type, self)
SpecializationUtil.createSpecializationEnvironments(self, function(specialization, variable)
self:setLoadingState(HandToolLoadingState.ERROR)
end)
SpecializationUtil.raiseEvent(self, "onPreLoad", self.savegame)
if self.loadingState ~= HandToolLoadingState.OK then
printWarning("Handtool pre-loading failed!")
return false
end
self.i3dFilename = modDirectory .. self.xmlFile:getValue("handTool.base.filename")
if self.i3dFilename == nil then
self:loadFinishedNonStoreItem()
else
self:setLoadingStep(SpecializationLoadStep.AWAIT_I3D)
self.sharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync(self.i3dFilename, true, false, self.i3dFileLoadedNonStoreItem, self)
end
return nil
end
function HandTool:loadFinishedNonStoreItem()
self:setLoadingState(HandToolLoadingState.OK)
self:setLoadingStep(SpecializationLoadStep.LOAD)
self.age = 0
self:setOwnerFarmId(self.handToolLoadingData.ownerFarmId, true)
self.mass = self.xmlFile:getValue("handTool.base#mass", 0)
local savegame = self.savegame
if savegame ~= nil then
local uniqueId = savegame.xmlFile:getValue(savegame.key .. "#uniqueId", nil)
if uniqueId ~= nil then self:setUniqueId(uniqueId) end
local farmId = savegame.xmlFile:getValue(savegame.key .. "#farmId", AccessHandler.EVERYONE)
if g_farmManager.mergedFarms ~= nil and g_farmManager.mergedFarms[farmId] ~= nil then farmId = g_farmManager.mergedFarms[farmId] end
self:setOwnerFarmId(farmId, true)
end
self.typeDesc = self.xmlFile:getValue("handTool.base.typeDesc", "TypeDescription", self.customEnvironment, true)
self.activateText = self.xmlFile:getValue("handTool.base.actions#activate", "Activate", self.customEnvironment, true)
if self.i3dNode ~= nil then
self.rootNode = getChildAt(self.i3dNode, 0)
self.components = {}
I3DUtil.loadI3DComponents(self.i3dNode, self.components)
self.i3dMappings = {}
I3DUtil.loadI3DMapping(self.xmlFile, "handTool", self.rootLevelNodes, self.i3dMappings)
for _, component in ipairs(self.components) do
link(getRootNode(), component.node)
end
delete(self.i3dNode)
self.i3dNode = nil
self.graphicalNode = self.xmlFile:getValue("handTool.base.graphics#node", nil, self.components, self.i3dMappings)
if self.graphicalNode == nil then
Logging.xmlError(self.xmlFile, "Handtool is missing graphical node! Graphics will not work as intended!")
self:setLoadingState(HandToolLoadingState.ERROR)
self:loadCallback()
return
end
self.graphicalNodeParent = getParent(self.graphicalNode)
self.handNode = self.xmlFile:getValue("handTool.base.handNode#node", nil, self.components, self.i3dMappings)
self.useLeftHand = self.xmlFile:getValue("handTool.base.handNode#useLeftHand", self.useLeftHand)
self.firstPersonNode = self.xmlFile:getValue("handTool.base.firstPersonNode#node", nil, self.components, self.i3dMappings)
end
self.shouldLockFirstPerson = self.xmlFile:getValue("handTool.base.graphics#lockFirstPerson", nil)
self.runMultiplier = self.xmlFile:getValue("handTool.base#runMultiplier", 1)
self.walkMultiplier = self.xmlFile:getValue("handTool.base#walkMultiplier", 1)
self.canCrouch = self.xmlFile:getValue("handTool.base#canCrouch", true)
self.mustBeHeld = self.xmlFile:getValue("handTool.base#mustBeHeld", false)
self.canBeSaved = self.xmlFile:getValue("handTool.base#canBeSaved", true)
self.canBeDropped = self.xmlFile:getValue("handTool.base#canBeDropped", true)
SpecializationUtil.raiseEvent(self, "onLoad", self.xmlFile, self.baseDirectory)
if self.loadingState == HandToolLoadingState.OK then
self:setLoadingStep(SpecializationLoadStep.POST_LOAD)
SpecializationUtil.raiseEvent(self, "onPostLoad", self.savegame)
if self.loadingState == HandToolLoadingState.OK then
if savegame ~= nil then
self.age = savegame.xmlFile:getValue(savegame.key .. "#age", 0)
self.price = savegame.xmlFile:getValue(savegame.key .. "#price", self.price)
end
local v43 = g_currentMission
if v43 ~= nil and v43.environment ~= nil then
g_messageCenter:subscribe(MessageType.PERIOD_CHANGED, self.periodChanged, self)
end
if #self.loadingTasks == 0 then
self:onFinishedLoadingNonStoreItem()
else
self.readyForFinishLoading = true
self:setLoadingStep(SpecializationLoadStep.AWAIT_SUB_I3D)
end
else
Logging.xmlError(self.xmlFile, "HandTool post-loading failed!")
self:loadCallback()
return
end
else
Logging.xmlError(self.xmlFile, "HandTool loading failed!")
self:loadCallback()
return
end
end
function HandTool:i3dFileLoadedNonStoreItem(node)
if node == 0 then
self:setLoadingState(HandToolLoadingState.ERROR)
printError("Handtool i3d loading failed!")
self:loadCallback()
else
self.i3dNode = node
setVisibility(node, false)
self:loadFinishedNonStoreItem()
end
end
function HandTool:onFinishedLoadingNonStoreItem()
self:setLoadingStep(SpecializationLoadStep.FINISHED)
SpecializationUtil.raiseEvent(self, "onLoadFinished", self.savegame)
if self.isServer then
self:setLoadingStep(SpecializationLoadStep.SYNCHRONIZED)
end
self.finishedLoading = true
if g_currentMission.handToolSystem:addHandTool(self) then
if self.handToolLoadingData.isRegistered then
self:register()
end
local holder = self.handToolLoadingData.holder
if holder == nil then
if self.savegame ~= nil then
self.pendingHolderUniqueId = self.savegame.xmlFile:getValue(self.savegame.key .. ".holder#uniqueId", nil)
end
elseif holder:getCanPickupHandTool(self) then
self.pendingHolder = holder
self:setHolder(holder)
end
g_currentMission:addOwnedItem(self)
self.savegame = nil
self.handToolLoadingData = nil
self.xmlFile:delete()
self.xmlFile = nil
if self.externalSoundsFile ~= nil then
self.externalSoundsFile:delete()
self.externalSoundsFile = nil
end
self:loadCallback()
else
Logging.xmlError(self.xmlFile, "Failed to register handTool!")
self:setLoadingState(HandToolLoadingState.ERROR)
self:loadCallback()
end
end
================================================
FILE: src/handTools/HandToolSystem.lua
================================================
RL_HandToolSystem = {}
local modName = g_currentModName
function RL_HandToolSystem:loadHandToolFromXML(superFunc, xmlFile, key)
local returnValue = superFunc(self, xmlFile, key)
if returnValue then return true end
local filename = NetworkUtil.convertFromNetworkFilename(xmlFile:getValue(key .. "#filename"))
if not string.contains(filename, modName) then return false end
local tempXml = XMLFile.loadIfExists("tempHandTool", filename, HandTool.xmlSchema)
if tempXml == nil then return false end
local typeName = tempXml:getValue("handTool#type")
tempXml:delete()
self.handToolsToLoad = self.handToolsToLoad + 1
local type = g_handToolTypeManager:getTypeByName(modName .. "." .. typeName)
local handTool = _G[type.className].new(g_currentMission:getIsServer(), g_currentMission:getIsClient())
handTool:setType(type)
handTool:setLoadCallback(self.loadHandToolFinished, self)
handTool:loadNonStoreItem({ ["savegameData"] = { ["xmlFile"] = xmlFile, ["key"] = key } }, filename)
return true
end
HandToolSystem.loadHandToolFromXML = Utils.overwrittenFunction(HandToolSystem.loadHandToolFromXML, RL_HandToolSystem.loadHandToolFromXML)
================================================
FILE: src/handTools/RLHandTools.lua
================================================
RLHandTools = {}
local modDirectory = g_currentModDirectory
local modName = g_currentModName
local path = modDirectory .. "xml/handTools.xml"
local xmlFile = XMLFile.loadIfExists("rlHandTools", path)
RLHandTools.xmlPaths = {}
if xmlFile ~= nil then
xmlFile:iterate("handTools.specializations.specialization", function(_, key)
local name = xmlFile:getString(key .. "#name")
local className = xmlFile:getString(key .. "#className")
local filename = xmlFile:getString(key .. "#filename")
g_handToolSpecializationManager:addSpecialization(name, className, modDirectory .. filename)
end)
xmlFile:iterate("handTools.types.type", function(_, key)
g_handToolTypeManager:loadTypeFromXML(xmlFile.handle, key, false, nil, modName)
RLHandTools.xmlPaths[xmlFile:getString(key .. "#name")] = modDirectory .. xmlFile:getString(key .. "#xmlFile")
end)
end
================================================
FILE: src/handTools/specializations/HandToolAIStraw.lua
================================================
HandToolAIStraw = {}
HandToolAIStraw.numHeldStraws = 0
local specName = "spec_FS25_RealisticLivestock.aiStraw"
function HandToolAIStraw.registerFunctions(handTool)
SpecializationUtil.registerFunction(handTool, "setAnimal", HandToolAIStraw.setAnimal)
SpecializationUtil.registerFunction(handTool, "setDewarUniqueId", HandToolAIStraw.setDewarUniqueId)
SpecializationUtil.registerFunction(handTool, "showInfo", HandToolAIStraw.showInfo)
SpecializationUtil.registerFunction(handTool, "updateStraw", HandToolAIStraw.updateStraw)
SpecializationUtil.registerFunction(handTool, "renderErrorText", HandToolAIStraw.renderErrorText)
SpecializationUtil.registerFunction(handTool, "getBelongsToDewar", HandToolAIStraw.getBelongsToDewar)
SpecializationUtil.registerFunction(handTool, "onInseminate", HandToolAIStraw.onInseminate)
SpecializationUtil.registerFunction(handTool, "onReturnToDewar", HandToolAIStraw.onReturnToDewar)
end
function HandToolAIStraw.registerOverwrittenFunctions(handTool)
SpecializationUtil.registerOverwrittenFunction(handTool, "getShowInHandToolsOverview", HandToolAIStraw.getShowInHandToolsOverview)
end
function HandToolAIStraw.registerEventListeners(handTool)
SpecializationUtil.registerEventListener(handTool, "onPostLoad", HandToolAIStraw)
SpecializationUtil.registerEventListener(handTool, "onDelete", HandToolAIStraw)
SpecializationUtil.registerEventListener(handTool, "onDraw", HandToolAIStraw)
SpecializationUtil.registerEventListener(handTool, "onHeldStart", HandToolAIStraw)
SpecializationUtil.registerEventListener(handTool, "onHeldEnd", HandToolAIStraw)
SpecializationUtil.registerEventListener(handTool, "onRegisterActionEvents", HandToolAIStraw)
SpecializationUtil.registerEventListener(handTool, "onReadStream", HandToolAIStraw)
SpecializationUtil.registerEventListener(handTool, "onWriteStream", HandToolAIStraw)
end
function HandToolAIStraw.prerequisitesPresent()
print("Loaded handTool: HandToolAIStraw")
return true
end
function HandToolAIStraw:onPostLoad(savegame)
HandToolAIStraw.numHeldStraws = HandToolAIStraw.numHeldStraws + 1
local spec = self[specName]
if self.isClient then spec.defaultCrosshair = self:createCrosshairOverlay("gui.crosshairDefault") end
spec.inseminateText = g_i18n:getText("rl_ui_inseminateAnimal")
spec.returnText = "Return straw"
spec.isEmpty = false
spec.textAlpha = 1
spec.textAlphaReverse = false
if savegame == nil or savegame.xmlFile == nil then return end
local xmlFile, key = savegame.xmlFile, savegame.key
local animalKey = key .. ".FS25_RealisticLivestock.aiStraw.animal"
spec.isEmpty = xmlFile:getBool(key .. ".FS25_RealisticLivestock.aiStraw#isEmpty", false)
spec.dewarUniqueId = xmlFile:getString(key .. ".FS25_RealisticLivestock.aiStraw#dewarUniqueId")
if xmlFile:hasProperty(animalKey) then
local animal = {}
animal.country = xmlFile:getInt(animalKey .. "#country")
animal.farmId = xmlFile:getString(animalKey .. "#farmId")
animal.uniqueId = xmlFile:getString(animalKey .. "#uniqueId")
animal.name = xmlFile:getString(animalKey .. "#name")
animal.typeIndex = xmlFile:getInt(animalKey .. "#typeIndex")
animal.subTypeIndex = xmlFile:getInt(animalKey .. "#subTypeIndex")
animal.success = xmlFile:getFloat(animalKey .. "#success")
animal.genetics = {
["metabolism"] = xmlFile:getFloat(animalKey .. ".genetics#metabolism"),
["fertility"] = xmlFile:getFloat(animalKey .. ".genetics#fertility"),
["health"] = xmlFile:getFloat(animalKey .. ".genetics#health"),
["quality"] = xmlFile:getFloat(animalKey .. ".genetics#quality"),
["productivity"] = xmlFile:getFloat(animalKey .. ".genetics#productivity")
}
self.animal = animal
end
end
function HandToolAIStraw:onDelete()
HandToolAIStraw.numHeldStraws = HandToolAIStraw.numHeldStraws - 1
local spec = self[specName]
if spec.defaultCrosshair ~= nil then
spec.defaultCrosshair:delete()
spec.defaultCrosshair = nil
end
end
function HandToolAIStraw:saveToXMLFile(xmlFile, key)
local animal = self[specName].animal
xmlFile:setBool(key .. "#isEmpty", self[specName].isEmpty or false)
xmlFile:setString(key .. "#dewarUniqueId", self[specName].dewarUniqueId or "")
if animal ~= nil then
xmlFile:setInt(key .. ".animal#country", animal.country)
xmlFile:setString(key .. ".animal#farmId", animal.farmId)
xmlFile:setString(key .. ".animal#uniqueId", animal.uniqueId)
xmlFile:setString(key .. ".animal#name", animal.name)
xmlFile:setInt(key .. ".animal#typeIndex", animal.typeIndex)
xmlFile:setInt(key .. ".animal#subTypeIndex", animal.subTypeIndex)
xmlFile:setFloat(key .. ".animal#success", animal.success)
for type, value in pairs(animal.genetics) do
xmlFile:setFloat(key .. ".animal.genetics#" .. type, value)
end
end
end
function HandToolAIStraw:onReadStream(streamId, connection)
local spec = self[specName]
spec.isEmpty = streamReadBool(streamId)
spec.dewarUniqueId = streamReadString(streamId)
local hasAnimal = streamReadBool(streamId)
local animal
if hasAnimal then
local animal = { ["genetics"] = {} }
animal.country = streamReadUInt8(streamId)
animal.farmId = streamReadString(streamId)
animal.uniqueId = streamReadString(streamId)
animal.name = streamReadString(streamId)
animal.typeIndex = streamReadUInt8(streamId)
animal.subTypeIndex = streamReadUInt8(streamId)
animal.success = streamReadFloat32(streamId)
animal.genetics.metabolism = streamReadFloat32(streamId)
animal.genetics.fertility = streamReadFloat32(streamId)
animal.genetics.health = streamReadFloat32(streamId)
animal.genetics.quality = streamReadFloat32(streamId)
animal.genetics.productivity = streamReadFloat32(streamId)
if animal.genetics.productivity < 0 then animal.genetics.productivity = nil end
end
spec.animal = animal
end
function HandToolAIStraw:onWriteStream(streamId, connection)
local spec = self[specName]
streamWriteBool(streamId, spec.isEmpty or false)
streamWriteString(streamId, spec.dewarUniqueId or "")
streamWriteBool(streamId, spec.animal ~= nil)
if spec.animal ~= nil then
local animal = spec.animal
streamWriteUInt8(streamId, animal.country)
streamWriteString(streamId, animal.farmId)
streamWriteString(streamId, animal.uniqueId)
streamWriteString(streamId, animal.name or "")
streamWriteUInt8(streamId, animal.typeIndex)
streamWriteUInt8(streamId, animal.subTypeIndex)
streamWriteFloat32(streamId, animal.success)
streamWriteFloat32(streamId, animal.genetics.metabolism)
streamWriteFloat32(streamId, animal.genetics.fertility)
streamWriteFloat32(streamId, animal.genetics.health)
streamWriteFloat32(streamId, animal.genetics.quality)
streamWriteFloat32(streamId, animal.genetics.productivity or -1)
end
end
function HandToolAIStraw:updateStraw(dT)
local player = self:getCarryingPlayer()
if player == nil then
spec.textAlpha = 1
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
return
end
local spec = self[specName]
spec.targetedPlaceable, spec.targetedAnimal, spec.targetedDewar = nil, nil, nil
if not player.isOwner then
spec.textAlpha = 1
spec.textAlphaReverse = false
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
return
end
local node = player.targeter:getClosestTargetedNodeFromType(HandToolAIStraw)
if node == nil or node == 0 then
spec.textAlpha = 1
spec.textAlphaReverse = false
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
return
end
local placeable, animal = HandToolAIStraw.getHusbandryAndClusterFromNode(player, node)
if placeable == nil or animal == nil then
local object = g_currentMission:getNodeObject(node)
if object ~= nil and object:isa(Dewar) then
if self:getBelongsToDewar(object) then
spec.targetedDewar = object
spec.actionContext = "return"
g_inputBinding:setActionEventActive(spec.activateActionEventId, true)
g_inputBinding:setActionEventText(spec.activateActionEventId, spec.returnText)
else
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
self:renderErrorText("This straw can not be returned to this dewar")
end
else
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
spec.textAlpha = 1
spec.textAlphaReverse = false
end
return
end
local canBeInseminated, error = animal:getCanBeInseminatedByAnimal(spec.animal)
if spec.isEmpty or not canBeInseminated then
if spec.isEmpty then error = g_i18n:getText("rl_ui_strawEmpty") end
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
self:renderErrorText(error)
return
end
spec.targetedPlaceable, spec.targetedAnimal = placeable, animal
spec.actionContext = "inseminate"
g_inputBinding:setActionEventActive(spec.activateActionEventId, true)
g_inputBinding:setActionEventText(spec.activateActionEventId, string.format(spec.inseminateText, animal:getIdentifiers()))
end
function HandToolAIStraw:onHeldStart()
if g_localPlayer == nil or self:getCarryingPlayer() ~= g_localPlayer or not g_localPlayer.isOwner then return end
g_localPlayer.targeter:addTargetType(HandToolAIStraw, CollisionFlag.ANIMAL + CollisionFlag.DYNAMIC_OBJECT, 0.5, 3)
g_localPlayer.hudUpdater:setCarriedItem(self)
g_aiStrawUpdater:setStraw(self)
g_currentMission:addUpdateable(g_aiStrawUpdater)
local spec = self[specName]
spec.textAlpha = 1
spec.textAlphaReverse = false
end
function HandToolAIStraw:onHeldEnd()
if g_localPlayer == nil or g_localPlayer.hudUpdater:getCarriedItem() ~= self then return end
if g_localPlayer.isOwner then g_localPlayer.targeter:removeTargetType(HandToolAIStraw) end
g_localPlayer.hudUpdater:setCarriedItem()
g_aiStrawUpdater:setStraw()
g_currentMission:removeUpdateable(g_aiStrawUpdater)
end
function HandToolAIStraw:onRegisterActionEvents()
if self:getIsActiveForInput(true) then
local _, eventId = self:addActionEvent(InputAction.ACTIVATE_HANDTOOL, self, HandToolAIStraw.onActionFired, false, true, false, true, nil)
self[specName].activateActionEventId = eventId
g_inputBinding:setActionEventTextPriority(eventId, GS_PRIO_VERY_HIGH)
g_inputBinding:setActionEventText(eventId, "")
g_inputBinding:setActionEventActive(eventId, false)
end
end
function HandToolAIStraw:setAnimal(animal)
self[specName].animal = animal
end
function HandToolAIStraw:setDewarUniqueId(dewarUniqueId)
self[specName].dewarUniqueId = dewarUniqueId
end
function HandToolAIStraw:showInfo(box)
local animal = self[specName].animal
if animal == nil then return end
local animalSystem = g_currentMission.animalSystem
local subType = animalSystem:getSubTypeByIndex(animal.subTypeIndex)
box:addLine(g_i18n:getText("rl_ui_averageSuccess"), string.format("%s%%", tostring(math.round(animal.success * 100))))
box:addLine(g_i18n:getText("rl_ui_species"), animalSystem:getTypeByIndex(animal.typeIndex).groupTitle)
box:addLine(g_i18n:getText("infohud_type"), g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex))
box:addLine(g_i18n:getText("infohud_name"), animal.name)
box:addLine(g_i18n:getText("rl_ui_earTag"), string.format("%s %s %s", RealisticLivestock.AREA_CODES[animal.country].code, animal.farmId, animal.uniqueId))
for type, value in pairs(animal.genetics) do
local valueText
if value >= 1.65 then
valueText = "extremelyHigh"
elseif value >= 1.4 then
valueText = "veryHigh"
elseif value >= 1.1 then
valueText = "high"
elseif value >= 0.9 then
valueText = "average"
elseif value >= 0.7 then
valueText = "low"
elseif value >= 0.35 then
valueText = "veryLow"
else
valueText = "extremelyLow"
end
box:addLine(g_i18n:getText("rl_ui_" .. type), g_i18n:getText("rl_ui_genetics_" .. valueText))
end
end
function HandToolAIStraw.getHusbandryAndClusterFromNode(player, node)
if node == nil or not entityExists(node) then return nil, nil end
local husbandryId, animalId = getAnimalFromCollisionNode(node)
if husbandryId ~= nil and husbandryId ~= 0 then
local clusterHusbandry = g_currentMission.husbandrySystem:getClusterHusbandryById(husbandryId)
if clusterHusbandry ~= nil then
local placeable = clusterHusbandry:getPlaceable()
local animal = clusterHusbandry:getClusterByAnimalId(animalId, husbandryId)
if animal ~= nil and (g_currentMission.accessHandler:canFarmAccess(player.farmId, placeable) and (animal.changeDirt ~= nil and animal.getName ~= nil)) then return placeable, animal end
end
end
return nil, nil
end
function HandToolAIStraw:onActionFired()
local spec = self[specName]
if spec.actionContext == "inseminate" then
self:onInseminate()
elseif spec.actionContext == "return" then
self:onReturnToDewar()
end
end
function HandToolAIStraw:onInseminate()
local spec = self[specName]
local husbandry, animal = spec.targetedPlaceable, spec.targetedAnimal
animal:setInsemination(spec.animal)
if g_server ~= nil then
g_server:broadcastEvent(AnimalInseminationEvent.new(husbandry, animal, spec.animal))
elseif g_client ~= nil then
g_client:getServerConnection():sendEvent(AnimalInseminationEvent.new(husbandry, animal, spec.animal))
end
spec.isEmpty = true
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
if self.isServer then g_currentMission.handToolSystem:markHandToolForDeletion(self) end
end
function HandToolAIStraw:onReturnToDewar()
local spec = self[specName]
local dewar = spec.targetedDewar
if g_server ~= nil then
g_server:broadcastEvent(ReturnStrawEvent.new(dewar))
elseif g_client ~= nil then
g_client:getServerConnection():sendEvent(ReturnStrawEvent.new(dewar))
end
dewar:changeStraws(1)
spec.isEmpty = true
g_inputBinding:setActionEventActive(spec.activateActionEventId, false)
if self.isServer then g_currentMission.handToolSystem:markHandToolForDeletion(self) end
end
function HandToolAIStraw:getShowInHandToolsOverview()
return false
end
function HandToolAIStraw:renderErrorText(text)
local spec = self[specName]
setTextAlignment(RenderText.ALIGN_CENTER)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_MIDDLE)
setTextColor(1, 0, 0, spec.textAlpha)
renderText(0.5, 0.6, 0.023, text)
spec.textAlpha = spec.textAlpha + (spec.textAlphaReverse and 0.015 or -0.015)
if spec.textAlpha <= 0 or spec.textAlpha >= 1 then spec.textAlphaReverse = not spec.textAlphaReverse end
end
function HandToolAIStraw:getBelongsToDewar(dewar)
local spec = self[specName]
return dewar:getUniqueId() == spec.dewarUniqueId
end
function HandToolAIStraw:onDraw()
self[specName].defaultCrosshair:render()
end
================================================
FILE: src/handTools/specializations/HandToolHorseBrush.lua
================================================
RL_HandToolHorseBrush = {}
function RL_HandToolHorseBrush:getHusbandryAndClusterFromNode(superFunc, node)
if node == nil or not entityExists(node) then return nil, nil end
local husbandryId, animalId = getAnimalFromCollisionNode(node)
if husbandryId ~= nil and husbandryId ~= 0 then
local clusterHusbandry = g_currentMission.husbandrySystem:getClusterHusbandryById(husbandryId)
if clusterHusbandry ~= nil then
local placeable = clusterHusbandry:getPlaceable()
local animal = clusterHusbandry:getClusterByAnimalId(animalId, husbandryId)
if animal ~= nil and (g_currentMission.accessHandler:canFarmAccess(self.farmId, placeable) and (animal.changeDirt ~= nil and animal.getName ~= nil)) then return placeable, animal end
end
end
return nil, nil
end
HandToolHorseBrush.getHusbandryAndClusterFromNode = Utils.overwrittenFunction(HandToolHorseBrush.getHusbandryAndClusterFromNode, RL_HandToolHorseBrush.getHusbandryAndClusterFromNode)
================================================
FILE: src/objects/Dewar.lua
================================================
Dewar = {}
Dewar.CAPACITY = 1000
Dewar.PRICE_PER_STRAW = 0.85
local dewar_mt = Class(Dewar, PhysicsObject)
local modDirectory = g_currentModDirectory
function Dewar.new(isServer, isClient)
local self = PhysicsObject.new(isServer, isClient, dewar_mt)
registerObjectClassName(self, "Dewar")
self.uniqueId = nil
self.position = nil
self.rotation = nil
self.sharedRequestId = nil
self.mass = 0.1
self.isAddedToItemSystem = false
self.straws = 0
self.texts = {}
return self
end
function Dewar:delete()
g_dewarManager:removeDewar(self:getOwnerFarmId(), self)
if self.sharedRequestId ~= nil then
g_i3DManager:releaseSharedI3DFile(self.sharedRequestId)
self.sharedRequestId = nil
end
unregisterObjectClassName(self)
if self.isAddedToItemSystem then g_currentMission.itemSystem:removeItem(self) end
Dewar:superClass().delete(self)
end
function Dewar:register(position, rotation, animal, quantity)
--if self.isServer then Dewar:superClass().register(self, true) end
self.position = self.position or position
self.rotation = self.rotation or rotation
self.mass = 0.1
if self.nodeId == nil or self.nodeId == 0 then self:createNode(modDirectory .. "objects/dewar/dewar.i3d") end
local x, y, z = unpack(self.position)
local rx, ry, rz = unpack(self.rotation)
local node = self.nodeId
link(getRootNode(), node)
setWorldTranslation(node, unpack(self.position))
setWorldRotation(node, unpack(self.rotation))
local sx, sy, sz = getWorldTranslation(self.shapeNode)
local srx, sry, srz = getWorldRotation(self.shapeNode)
self.ox, self.oy, self.oz = x - sx, y - sy, z - sz
self.orx, self.ory, self.orz = rx - srx, ry - sry, rz - srz
if not self.isAddedToItemSystem then
g_currentMission.itemSystem:addItem(self)
self.isAddedToItemSystem = true
end
if animal ~= nil then self:setAnimal(animal) end
if quantity ~= nil then self:setStraws(quantity) end
g_dewarManager:addDewar(self:getOwnerFarmId(), self)
self:updateStrawVisuals()
self:updateAnimalVisuals()
--if g_server ~= nil then
--g_server:addObject(self, string.format("dewar_%s", self.uniqueId))
--elseif g_client ~= nil then
--g_client:addObject(self, string.format("dewar_%s", self.uniqueId))
--end
end
function Dewar:saveToXMLFile(xmlFile, key)
local x, y, z = getWorldTranslation(self.shapeNode)
local rx, ry, rz = getWorldRotation(self.shapeNode)
xmlFile:setString(key .. "#uniqueId", self.uniqueId)
xmlFile:setVector(key .. "#position", table.pack(x + self.ox, y + self.oy, z + self.oz))
xmlFile:setVector(key .. "#rotation", table.pack(rx + self.orx, ry + self.ory, rz + self.orz))
xmlFile:setInt(key .. "#farmId", self:getOwnerFarmId())
xmlFile:setInt(key .. "#straws", self.straws)
local animalKey = key .. ".animal"
local animal = self.animal
if animal ~= nil then
xmlFile:setInt(animalKey .. "#country", animal.country)
xmlFile:setString(animalKey .. "#farmId", animal.farmId)
xmlFile:setString(animalKey .. "#uniqueId", animal.uniqueId)
xmlFile:setString(animalKey .. "#name", animal.name)
xmlFile:setInt(animalKey .. "#typeIndex", animal.typeIndex)
xmlFile:setInt(animalKey .. "#subTypeIndex", animal.subTypeIndex)
xmlFile:setFloat(animalKey .. "#success", animal.success)
for type, value in pairs(animal.genetics) do
xmlFile:setFloat(animalKey .. ".genetics#" .. type, value)
end
end
end
function Dewar:loadFromXMLFile(xmlFile, key)
self.uniqueId = xmlFile:getString(key .. "#uniqueId")
self.position = xmlFile:getVector(key .. "#position")
self.rotation = xmlFile:getVector(key .. "#rotation")
self:setOwnerFarmId(xmlFile:getInt(key .. "#farmId"))
self.straws = xmlFile:getInt(key .. "#straws")
local animalKey = key .. ".animal"
if xmlFile:hasProperty(animalKey) then
local animal = {}
animal.country = xmlFile:getInt(animalKey .. "#country")
animal.farmId = xmlFile:getString(animalKey .. "#farmId")
animal.uniqueId = xmlFile:getString(animalKey .. "#uniqueId")
animal.name = xmlFile:getString(animalKey .. "#name")
animal.typeIndex = xmlFile:getInt(animalKey .. "#typeIndex")
animal.subTypeIndex = xmlFile:getInt(animalKey .. "#subTypeIndex")
animal.success = xmlFile:getFloat(animalKey .. "#success")
animal.genetics = {
["metabolism"] = xmlFile:getFloat(animalKey .. ".genetics#metabolism"),
["fertility"] = xmlFile:getFloat(animalKey .. ".genetics#fertility"),
["health"] = xmlFile:getFloat(animalKey .. ".genetics#health"),
["quality"] = xmlFile:getFloat(animalKey .. ".genetics#quality"),
["productivity"] = xmlFile:getFloat(animalKey .. ".genetics#productivity")
}
self.animal = animal
end
self.isAddedToItemSystem = true
return true
end
function Dewar:readStream(streamId, connection)
self.uniqueId = streamReadString(streamId)
self.position = {
streamReadFloat32(streamId),
streamReadFloat32(streamId),
streamReadFloat32(streamId)
}
self.rotation = {
streamReadFloat32(streamId),
streamReadFloat32(streamId),
streamReadFloat32(streamId)
}
self:setOwnerFarmId(streamReadUInt8(streamId))
self.straws = streamReadUInt16(streamId)
local hasAnimal = streamReadBool(streamId)
local animal
if hasAnimal then
animal = { ["genetics"] = {} }
animal.country = streamReadUInt8(streamId)
animal.farmId = streamReadString(streamId)
animal.uniqueId = streamReadString(streamId)
animal.name = streamReadString(streamId)
animal.typeIndex = streamReadUInt8(streamId)
animal.subTypeIndex = streamReadUInt8(streamId)
animal.success = streamReadFloat32(streamId)
animal.genetics.metabolism = streamReadFloat32(streamId)
animal.genetics.fertility = streamReadFloat32(streamId)
animal.genetics.health = streamReadFloat32(streamId)
animal.genetics.quality = streamReadFloat32(streamId)
animal.genetics.productivity = streamReadFloat32(streamId)
if animal.genetics.productivity < 0 then animal.genetics.productivity = nil end
end
self.animal = animal
Dewar:superClass().readStream(self, streamId, connection)
end
function Dewar:writeStream(streamId, connection)
streamWriteString(streamId, self.uniqueId)
streamWriteFloat32(streamId, self.position[1])
streamWriteFloat32(streamId, self.position[2])
streamWriteFloat32(streamId, self.position[3])
streamWriteFloat32(streamId, self.rotation[1])
streamWriteFloat32(streamId, self.rotation[2])
streamWriteFloat32(streamId, self.rotation[3])
streamWriteUInt8(streamId, self:getOwnerFarmId())
streamWriteUInt16(streamId, self.straws)
streamWriteBool(streamId, self.animal ~= nil)
if self.animal ~= nil then
local animal = self.animal
streamWriteUInt8(streamId, animal.country)
streamWriteString(streamId, animal.farmId)
streamWriteString(streamId, animal.uniqueId)
streamWriteString(streamId, animal.name or "")
streamWriteUInt8(streamId, animal.typeIndex)
streamWriteUInt8(streamId, animal.subTypeIndex)
streamWriteFloat32(streamId, animal.success)
streamWriteFloat32(streamId, animal.genetics.metabolism)
streamWriteFloat32(streamId, animal.genetics.fertility)
streamWriteFloat32(streamId, animal.genetics.health)
streamWriteFloat32(streamId, animal.genetics.quality)
streamWriteFloat32(streamId, animal.genetics.productivity or -1)
end
Dewar:superClass().writeStream(self, streamId, connection)
end
function Dewar:createNode(filename)
local node, sharedRequestId = g_i3DManager:loadSharedI3DFile(filename, true, true, true)
setVisibility(node, true)
self.sharedRequestId = sharedRequestId
self:setNodeId(node)
local shapeNode = getChildAt(node, 0)
setMass(shapeNode, self.mass)
self.shapeNode = shapeNode
end
function Dewar:getUniqueId()
return self.uniqueId
end
function Dewar:setUniqueId(uniqueId)
self.uniqueId = uniqueId
end
function Dewar:setVisibility(visibility)
setVisibility(self.nodeId, visibility)
end
function Dewar:setAnimal(animal)
self.animal =
{
["country"] = animal.birthday.country,
["farmId"] = animal.farmId,
["uniqueId"] = animal.uniqueId,
["name"] = animal:getName(),
["typeIndex"] = animal.animalTypeIndex,
["subTypeIndex"] = animal.subTypeIndex,
["genetics"] = table.clone(animal.genetics, 3),
["success"] = animal.success
}
self:updateAnimalVisuals()
end
function Dewar:getAnimal()
return self.animal
end
function Dewar:showInfo(box)
if self.animal == nil then return end
local animal = self.animal
local animalSystem = g_currentMission.animalSystem
local subType = animalSystem:getSubTypeByIndex(animal.subTypeIndex)
box:addLine(g_i18n:getText("rl_ui_strawMultiple"), tostring(self.straws))
box:addLine(g_i18n:getText("rl_ui_averageSuccess"), string.format("%s%%", tostring(math.round(animal.success * 100))))
box:addLine(g_i18n:getText("rl_ui_species"), animalSystem:getTypeByIndex(animal.typeIndex).groupTitle)
box:addLine(g_i18n:getText("infohud_type"), g_fillTypeManager:getFillTypeTitleByIndex(subType.fillTypeIndex))
box:addLine(g_i18n:getText("infohud_name"), animal.name)
box:addLine(g_i18n:getText("rl_ui_earTag"), string.format("%s %s %s", RealisticLivestock.AREA_CODES[animal.country].code, animal.farmId, animal.uniqueId))
for type, value in pairs(animal.genetics) do
local valueText
if value >= 1.65 then
valueText = "extremelyHigh"
elseif value >= 1.4 then
valueText = "veryHigh"
elseif value >= 1.1 then
valueText = "high"
elseif value >= 0.9 then
valueText = "average"
elseif value >= 0.7 then
valueText = "low"
elseif value >= 0.35 then
valueText = "veryLow"
else
valueText = "extremelyLow"
end
box:addLine(g_i18n:getText("rl_ui_" .. type), g_i18n:getText("rl_ui_genetics_" .. valueText))
end
end
function Dewar:getTotalMass()
return self.mass
end
function Dewar:getCanBePickedUp(player)
return true
end
function Dewar:setStraws(value)
self.straws = value or 0
self:updateStrawVisuals()
end
function Dewar:changeStraws(delta)
self.straws = math.clamp(self.straws + delta, 0, Dewar.CAPACITY)
if self.straws <= 0 then
self:delete()
return
end
self:updateStrawVisuals()
end
function Dewar:updateStrawVisuals()
local parent = I3DUtil.indexToObject(self.shapeNode, "0|1")
set3DTextRemoveSpaces(true)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_MIDDLE)
setTextAlignment(RenderText.ALIGN_CENTER)
setTextColor(1, 0.1, 0.1, 1)
set3DTextWordsPerLine(1)
setTextLineHeightScale(0.75)
setTextFont(RealisticLivestock.FONTS.toms_handwritten)
if self.texts.straws ~= nil then delete3DLinkedText(self.texts.straws) end
self.texts.straws = create3DLinkedText(parent, 0.003, 0.01, 0.003, 0, math.rad(-90), 0, 0.025, string.format("%s %s", self.straws, self.straws == 1 and "straw" or "straws"))
set3DTextRemoveSpaces(false)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_BASELINE)
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(1, 1, 1, 1)
set3DTextWordsPerLine(0)
setTextLineHeightScale(1.1)
setTextFont()
end
function Dewar:updateAnimalVisuals()
if self.animal == nil then return end
local parent = I3DUtil.indexToObject(self.shapeNode, "0|0")
local country = RealisticLivestock.AREA_CODES[self.animal.country].code
local farmId = self.animal.farmId
local uniqueId = self.animal.uniqueId
set3DTextRemoveSpaces(true)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_MIDDLE)
setTextAlignment(RenderText.ALIGN_CENTER)
setTextColor(1, 0.1, 0.1, 1)
set3DTextWordsPerLine(1)
setTextLineHeightScale(1.25)
setTextFont(RealisticLivestock.FONTS.toms_handwritten)
if self.texts.animal ~= nil then delete3DLinkedText(self.texts.animal) end
self.texts.animal = create3DLinkedText(parent, -0.01, -0.002, 0.008, 0, math.rad(-170), 0, 0.02, string.format("%s %s %s", country, uniqueId, farmId))
set3DTextAutoScale(false)
set3DTextRemoveSpaces(false)
setTextVerticalAlignment(RenderText.VERTICAL_ALIGN_BASELINE)
setTextAlignment(RenderText.ALIGN_LEFT)
setTextColor(1, 1, 1, 1)
set3DTextWordsPerLine(0)
setTextLineHeightScale(1.1)
setTextFont()
end
function Dewar:getTensionBeltNodeId()
return self.shapeNode
end
function Dewar:getSupportsTensionBelts()
return true
end
function Dewar:getMeshNodes()
return { self.shapeNode }
end
================================================
FILE: src/placeables/RealisticLivestock_PlaceableSystem.lua
================================================
RealisticLivestock_PlaceableSystem = {}
local modSettingsDirectory = g_currentModSettingsDirectory
function RealisticLivestock_PlaceableSystem:saveToXML(_, _)
createFolder(modSettingsDirectory)
local xmlFile = XMLFile.loadIfExists("RealisticLivestock", modSettingsDirectory .. "Settings.xml")
if xmlFile == nil then xmlFile = XMLFile.create("RealisticLivestock", modSettingsDirectory .. "Settings.xml", "Settings") end
if xmlFile ~= nil then
xmlFile:setInt("Settings.setting(0)#maxHusbandries", RealisticLivestock_AnimalClusterHusbandry.MAX_HUSBANDRIES)
xmlFile:save(false, true)
xmlFile:delete()
end
end
PlaceableSystem.saveToXML = Utils.prependedFunction(PlaceableSystem.saveToXML, RealisticLivestock_PlaceableSystem.saveToXML)
================================================
FILE: src/player/RealisticLivestock_PlayerHUDUpdater.lua
================================================
RealisticLivestock_PlayerHUDUpdater = {}
function PlayerHUDUpdater:setCarriedItem(item)
self.currentlyCarriedItem = item
end
function PlayerHUDUpdater:getCarriedItem()
return self.currentlyCarriedItem
end
function RealisticLivestock_PlayerHUDUpdater:update()
if Platform.playerInfo.showVehicleInfo and self.isDewar then self:showDewarInfo(self.object) end
if Platform.playerInfo.showVehicleInfo and self.currentlyCarriedItem ~= nil then self:showHandToolInfo(self.currentlyCarriedItem) end
end
PlayerHUDUpdater.update = Utils.appendedFunction(PlayerHUDUpdater.update, RealisticLivestock_PlayerHUDUpdater.update)
function RealisticLivestock_PlayerHUDUpdater:updateRaycastObject()
self.isDewar = false
if self.isAnimal == false and self.currentRaycastTarget ~= nil and entityExists(self.currentRaycastTarget) then
local object = g_currentMission:getNodeObject(self.currentRaycastTarget)
if object == nil then
if not getHasClassId(self.currentRaycastTarget, ClassIds.MESH_SPLIT_SHAPE) then
local husbandryId, animalId = getAnimalFromCollisionNode(self.currentRaycastTarget)
if husbandryId ~= nil and husbandryId ~= 0 then
local clusterHusbandry = g_currentMission.husbandrySystem:getClusterHusbandryById(husbandryId)
if clusterHusbandry ~= nil then
local animal = clusterHusbandry:getClusterByAnimalId(animalId, husbandryId)
if animal ~= nil then
self.isAnimal = true
self.object = animal
return
end
end
end
end
elseif object:isa(Dewar) then
self.isDewar = true
end
end
end
PlayerHUDUpdater.updateRaycastObject = Utils.appendedFunction(PlayerHUDUpdater.updateRaycastObject, RealisticLivestock_PlayerHUDUpdater.updateRaycastObject)
function PlayerHUDUpdater:showDewarInfo(object)
if object == nil then return end
local farmId = object:getOwnerFarmId()
if farmId == FarmManager.SPECTATOR_FARM_ID then return end
local box = self.objectBox
box:clear()
box:setTitle(g_i18n:getText("rl_ui_dewar"))
local farm = g_farmManager:getFarmById(farmId)
box:addLine(g_i18n:getText("fieldInfo_ownedBy"), self:convertFarmToName(farm))
object:showInfo(box)
box:showNextFrame()
end
function PlayerHUDUpdater:showHandToolInfo(object)
if object == nil then return end
if self.handToolBox == nil then self.handToolBox = g_currentMission.hud.infoDisplay:createBox(InfoDisplayKeyValueBox) end
local box = self.handToolBox
box:clear()
box:setTitle(g_i18n:getText("rl_ui_strawSingle"))
object:showInfo(box)
box:showNextFrame()
end
function RealisticLivestock_PlayerHUDUpdater:showAnimalInfo(animal)
if self.monitorBox == nil then self.monitorBox = g_currentMission.hud.infoDisplay:createBox(InfoDisplayKeyValueBox) end
if animal.monitor.active or animal.monitor.removed then
local box = self.monitorBox
box:clear()
box:setTitle(g_i18n:getText("rl_ui_monitor"))
animal:showMonitorInfo(box)
box:showNextFrame()
end
if self.geneticsBox == nil then self.geneticsBox = g_currentMission.hud.infoDisplay:createBox(RL_InfoDisplayKeyValueBox) end
local box = self.geneticsBox
box:clear()
box:setTitle(g_i18n:getText("rl_ui_genetics"))
animal:showGeneticsInfo(box)
box:showNextFrame()
if self.diseaseBox == nil then self.diseaseBox = g_currentMission.hud.infoDisplay:createBox(InfoDisplayKeyValueBox) end
local box = self.diseaseBox
box:clear()
if animal.diseases ~= nil and #animal.diseases > 0 and g_diseaseManager.diseasesEnabled then
box:setTitle(g_i18n:getText("rl_diseases"))
animal:showDiseasesInfo(box)
box:showNextFrame()
end
end
PlayerHUDUpdater.showAnimalInfo = Utils.appendedFunction(PlayerHUDUpdater.showAnimalInfo, RealisticLivestock_PlayerHUDUpdater.showAnimalInfo)
function RealisticLivestock_PlayerHUDUpdater:delete()
if self.geneticsBox ~= nil then g_currentMission.hud.infoDisplay:destroyBox(self.geneticsBox) end
if self.diseaseBox ~= nil then g_currentMission.hud.infoDisplay:destroyBox(self.diseaseBox) end
if self.monitorBox ~= nil then g_currentMission.hud.infoDisplay:destroyBox(self.monitorBox) end
if self.handToolBox ~= nil then g_currentMission.hud.infoDisplay:destroyBox(self.handToolBox) end
end
PlayerHUDUpdater.delete = Utils.appendedFunction(PlayerHUDUpdater.delete, RealisticLivestock_PlayerHUDUpdater.delete)
================================================
FILE: src/player/RealisticLivestock_PlayerInputComponent.lua
================================================
RealisticLivestock_PlayerInputComponent = {}
local modName = g_currentModName
function RealisticLivestock_PlayerInputComponent:update(_)
self.dewar = nil
if self.player.isOwner then
if g_inputBinding:getContextName() == PlayerInputComponent.INPUT_CONTEXT_NAME then
local currentMission = g_currentMission
local accessHandler = currentMission.accessHandler
local vehicleInRange = currentMission.interactiveVehicleInRange
local canAccess
if vehicleInRange == nil then
canAccess = false
else
canAccess = accessHandler:canPlayerAccess(vehicleInRange, self.player)
end
local closestNode = self.player.targeter:getClosestTargetedNodeFromType(PlayerInputComponent)
self.player.hudUpdater:setCurrentRaycastTarget(closestNode)
if not canAccess and closestNode ~= nil then
local husbandryId, animalId = getAnimalFromCollisionNode(closestNode)
if husbandryId ~= nil and husbandryId ~= 0 then
local clusterHusbandry = currentMission.husbandrySystem:getClusterHusbandryById(husbandryId)
if clusterHusbandry ~= nil then
local placeable = clusterHusbandry:getPlaceable()
local animal = clusterHusbandry:getClusterByAnimalId(animalId, husbandryId)
if animal ~= nil and (accessHandler:canFarmAccess(self.player.farmId, placeable) and animal:getRidableFilename() ~= nil) then
self.rideablePlaceable = placeable
self.rideableCluster = animal
local name = animal.getName == nil and "" or animal:getName()
local text = string.format(g_i18n:getText("action_rideAnimal"), name)
g_inputBinding:setActionEventText(self.enterActionId, text)
g_inputBinding:setActionEventActive(self.enterActionId, true)
end
end
else
local object = g_currentMission:getNodeObject(closestNode)
if object ~= nil and object:isa(Dewar) and object.straws > 0 then
self.dewar = object
g_inputBinding:setActionEventText(self.enterActionId, g_i18n:getText("rl_ui_takeStraw"))
g_inputBinding:setActionEventActive(self.enterActionId, true)
end
end
end
end
end
end
PlayerInputComponent.update = Utils.appendedFunction(PlayerInputComponent.update, RealisticLivestock_PlayerInputComponent.update)
function RealisticLivestock_PlayerInputComponent:onInputEnter()
if g_time <= g_currentMission.lastInteractionTime + 200 or g_currentMission.interactiveVehicleInRange ~= nil or self.rideablePlaceable ~= nil or self.dewar == nil or HandToolAIStraw.numHeldStraws > 10 then return end
local strawType = g_handToolTypeManager:getTypeByName(modName .. ".aiStraw")
local handTool = _G[strawType.className].new(g_currentMission:getIsServer(), g_currentMission:getIsClient())
handTool:setType(strawType)
handTool:setLoadCallback(self.onFinishedLoadStraw, self, { ["animal"] = self.dewar:getAnimal(), ["dewarUniqueId"] = self.dewar:getUniqueId() })
handTool:loadNonStoreItem({ ["ownerFarmId"] = g_localPlayer.farmId, ["isRegistered"] = false, ["holder"] = g_localPlayer }, RLHandTools.xmlPaths.aiStraw)
self.dewar:changeStraws(-1)
end
PlayerInputComponent.onInputEnter = Utils.appendedFunction(PlayerInputComponent.onInputEnter, RealisticLivestock_PlayerInputComponent.onInputEnter)
function PlayerInputComponent:onFinishedLoadStraw(handTool, loadingState, args)
if loadingState == HandToolLoadingState.OK then
handTool:setAnimal(args.animal)
handTool:setDewarUniqueId(args.dewarUniqueId)
end
end
function RealisticLivestock_PlayerInputComponent:registerGlobalPlayerActionEvents()
VisualAnimalsDialog.register()
g_inputBinding:registerActionEvent(InputAction.VisualAnimalsDialog, VisualAnimalsDialog, VisualAnimalsDialog.show, false, true, false, true, nil, true)
end
PlayerInputComponent.registerGlobalPlayerActionEvents = Utils.appendedFunction(PlayerInputComponent.registerGlobalPlayerActionEvents, RealisticLivestock_PlayerInputComponent.registerGlobalPlayerActionEvents)
function RealisticLivestock_PlayerInputComponent.onFinishedRideBlending(superFunc, _, args)
local placeable = args[1]
placeable:startRiding(args[2].farmId .. " " .. args[2].uniqueId, args[3])
end
PlayerInputComponent.onFinishedRideBlending = Utils.overwrittenFunction(PlayerInputComponent.onFinishedRideBlending, RealisticLivestock_PlayerInputComponent.onFinishedRideBlending)
================================================
FILE: src/vehicles/RealisticLivestock_VehicleSystem.lua
================================================
RealisticLivestock_VehicleSystem = {}
function RealisticLivestock_VehicleSystem:save(_, _)
local indexesToRemove = {}
for i, vehicle in ipairs(self.vehicles) do
if vehicle.spec_rideable ~= nil then table.insert(indexesToRemove, i) end
end
table.sort(indexesToRemove, function(a, b)
return a > b
end)
for i in indexesToRemove do
table.remove(self.vehicles, i)
end
end
VehicleSystem.save = Utils.prependedFunction(VehicleSystem.save, RealisticLivestock_VehicleSystem.save)
================================================
FILE: src/vehicles/specializations/RealisticLivestock_LivestockTrailer.lua
================================================
RealisticLivestock_LivestockTrailer = {}
function RealisticLivestock_LivestockTrailer:addAnimals(superFunc, animals)
for _, animal in pairs(animals) do
self:addCluster(animal)
end
end
LivestockTrailer.addAnimals = Utils.overwrittenFunction(LivestockTrailer.addAnimals, RealisticLivestock_LivestockTrailer.addAnimals)
function RealisticLivestock_LivestockTrailer:addCluster(superFunc, cluster)
local clusterSystem = self.spec_livestockTrailer.clusterSystem
if cluster.numAnimals > 1 or cluster.isIndividual == nil then
for i=1, cluster.numAnimals do
local subType = g_currentMission.animalSystem:getSubTypeByIndex(cluster.subTypeIndex)
local animal = Animal.new(cluster.age, cluster.health, nil, subType.gender, cluster.subTypeIndex, cluster.reproduction, false, false, false, clusterSystem)
clusterSystem:addCluster(animal)
end
clusterSystem:updateNow()
return
end
clusterSystem:addCluster(cluster)
clusterSystem:updateNow()
end
LivestockTrailer.addCluster = Utils.overwrittenFunction(LivestockTrailer.addCluster, RealisticLivestock_LivestockTrailer.addCluster)
function RealisticLivestock_LivestockTrailer:onLoadFinished(success)
if success == nil then return end
self.spec_livestockTrailer:updateAnimals()
end
LivestockTrailer.onLoadFinished = Utils.appendedFunction(LivestockTrailer.onLoadFinished, RealisticLivestock_LivestockTrailer.onLoadFinished)
function RealisticLivestock_LivestockTrailer:dayChanged(superFunc)
superFunc(self)
if self.isServer then
local minTemp = math.floor(g_currentMission.environment.weather.temperatureUpdater.currentMin)
local environment = g_currentMission.environment
local month = environment.currentPeriod + 2
local currentDayInPeriod = environment.currentDayInPeriod
if month > 12 then month = month - 12 end
local daysPerPeriod = environment.daysPerPeriod
local day = 1 + math.floor((currentDayInPeriod - 1) * (RealisticLivestock.DAYS_PER_MONTH[month] / daysPerPeriod))
local year = environment.currentYear
local spec = self.spec_livestockTrailer
local animals = spec.clusterSystem:getAnimals()
local totalChildren, deadParents, childrenToSell, childrenToSellMoney, lowHealthDeaths, oldAgeDeaths, randomDeaths, randomDeathsMoney = 0, 0, 0, 0, 0, 0, 0, 0
for _, animal in ipairs(animals) do
if animal.monthsSinceLastBirth == nil then
animal.monthsSinceLastBirth = 0
end
if animal.isParent == nil then
animal.isParent = false
end
local a, b, c, d, e, f, g, h = animal:onDayChanged(spec, self.isServer, day, month, year, currentDayInPeriod, daysPerPeriod)
totalChildren = totalChildren + a
deadParents = deadParents + b
childrenToSell = childrenToSell + c
childrenToSellMoney = childrenToSellMoney + d
lowHealthDeaths = lowHealthDeaths + e
oldAgeDeaths = oldAgeDeaths + f
randomDeaths = randomDeaths + g
randomDeathsMoney = randomDeathsMoney + h
end
local animalType = (spec.animalTypeIndex == AnimalType.COW and 1) or (spec.animalTypeIndex == AnimalType.PIG and 2) or (spec.animalTypeIndex == AnimalType.SHEEP and 3) or (spec.animalTypeIndex == AnimalType.CHICKEN and 4) or (spec.animalTypeIndex == AnimalType.HORSE and 5)
if totalChildren > 0 then
local msgText = ""
if animalType == 1 then msgText = totalChildren == 1 and g_i18n:getText("rl_ui_cow_singleBirth") or string.format(g_i18n:getText("rl_ui_cow_multipleBirths"), totalChildren) end
if animalType == 2 then msgText = totalChildren == 1 and g_i18n:getText("rl_ui_pig_singleBirth") or string.format(g_i18n:getText("rl_ui_pig_multipleBirths"), totalChildren) end
if animalType == 3 then msgText = totalChildren == 1 and g_i18n:getText("rl_ui_sheep_singleBirth") or string.format(g_i18n:getText("rl_ui_sheep_multipleBirths"), totalChildren) end
if animalType == 4 then msgText = totalChildren == 1 and g_i18n:getText("rl_ui_chicken_singleBirth") or string.format(g_i18n:getText("rl_ui_chicken_multipleBirths"), totalChildren) end
if animalType == 5 then msgText = totalChildren == 1 and g_i18n:getText("rl_ui_horse_singleBirth") or string.format(g_i18n:getText("rl_ui_horse_multipleBirths"), totalChildren) end
g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText)
end
if deadParents > 0 then
local msgText = ""
if animalType == 1 then msgText = deadParents == 1 and g_i18n:getText("rl_ui_cow_singleDeath_birth") or string.format(g_i18n:getText("rl_ui_cow_multipleDeaths_birth"), deadParents) end
if animalType == 2 then msgText = deadParents == 1 and g_i18n:getText("rl_ui_pig_singleDeath_birth") or string.format(g_i18n:getText("rl_ui_pig_multipleDeaths_birth"), deadParents) end
if animalType == 3 then msgText = deadParents == 1 and g_i18n:getText("rl_ui_sheep_singleDead_birth") or string.format(g_i18n:getText("rl_ui_sheep_multipleDeaths_birth"), deadParents) end
if animalType == 5 then msgText = deadParents == 1 and g_i18n:getText("rl_ui_horse_singleDeath_birth") or string.format(g_i18n:getText("rl_ui_horse_multipleDeaths_birth"), deadParents) end
g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText)
end
if childrenToSell > 0 and childrenToSellMoney > 0 then
local farmIndex = spec:getOwnerFarmId()
local farm = g_farmManager:getFarmById(farmIndex)
local msgText = ""
if animalType == 1 then msgText = childrenToSell == 1 and g_i18n:getText("rl_ui_cow_singleSold_birth") or string.format(g_i18n:getText("rl_ui_cow_multipleSold_birth"), childrenToSell) end
if animalType == 2 then msgText = childrenToSell == 1 and g_i18n:getText("rl_ui_pig_singleSold_birth") or string.format(g_i18n:getText("rl_ui_pig_multipleSold_birth"), childrenToSell) end
if animalType == 3 then msgText = childrenToSell == 1 and g_i18n:getText("rl_ui_sheep_singleSold_birth") or string.format(g_i18n:getText("rl_ui_sheep_multipleSold_birth"), childrenToSell) end
if animalType == 4 then msgText = childrenToSell == 1 and g_i18n:getText("rl_ui_chicken_singleSold_birth") or string.format(g_i18n:getText("rl_ui_chicken_multipleSold_birth"), childrenToSell) end
if animalType == 5 then msgText = childrenToSell == 1 and g_i18n:getText("rl_ui_horse_singleSold_birth") or string.format(g_i18n:getText("rl_ui_horse_multipleSold_birth"), childrenToSell) end
g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText)
if self.isServer then
g_currentMission:addMoneyChange(childrenToSellMoney, farmIndex, MoneyType.SOLD_ANIMALS, true)
else
g_client:getServerConnection():sendEvent(MoneyChangeEvent.new(childrenToSellMoney, MoneyType.SOLD_ANIMALS, farmIndex))
end
if farm ~= nil then
farm:changeBalance(childrenToSellMoney, MoneyType.SOLD_ANIMALS)
end
end
if lowHealthDeaths > 0 then
local msgText = ""
if animalType == 1 then msgText = lowHealthDeaths == 1 and g_i18n:getText("rl_ui_cow_singleDeath_health") or string.format(g_i18n:getText("rl_ui_cow_multipleDeaths_health"), lowHealthDeaths) end
if animalType == 2 then msgText = lowHealthDeaths == 1 and g_i18n:getText("rl_ui_pig_singleDeath_health") or string.format(g_i18n:getText("rl_ui_pig_multipleDeaths_health"), lowHealthDeaths) end
if animalType == 3 then msgText = lowHealthDeaths == 1 and g_i18n:getText("rl_ui_sheep_singleDeath_health") or string.format(g_i18n:getText("rl_ui_sheep_multipleDeaths_health"), lowHealthDeaths) end
if animalType == 4 then msgText = lowHealthDeaths == 1 and g_i18n:getText("rl_ui_chicken_singleDeath_health") or string.format(g_i18n:getText("rl_ui_chicken_multipleDeaths_health"), lowHealthDeaths) end
if animalType == 5 then msgText = lowHealthDeaths == 1 and g_i18n:getText("rl_ui_horse_singleDeath_health") or string.format(g_i18n:getText("rl_ui_horse_multipleDeaths_health"), lowHealthDeaths) end
g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText)
end
if oldAgeDeaths > 0 then
local msgText = ""
if animalType == 1 then msgText = oldAgeDeaths == 1 and g_i18n:getText("rl_ui_cow_singleDeath_age") or string.format(g_i18n:getText("rl_ui_cow_multipleDeaths_age"), oldAgeDeaths) end
if animalType == 2 then msgText = oldAgeDeaths == 1 and g_i18n:getText("rl_ui_pig_singleDeath_age") or string.format(g_i18n:getText("rl_ui_pig_multipleDeaths_age"), oldAgeDeaths) end
if animalType == 3 then msgText = oldAgeDeaths == 1 and g_i18n:getText("rl_ui_sheep_singleDeath_age") or string.format(g_i18n:getText("rl_ui_sheep_multipleDeaths_age"), oldAgeDeaths) end
if animalType == 4 then msgText = oldAgeDeaths == 1 and g_i18n:getText("rl_ui_chicken_singleDeath_age") or string.format(g_i18n:getText("rl_ui_chicken_multipleDeaths_age"), oldAgeDeaths) end
if animalType == 5 then msgText = oldAgeDeaths == 1 and g_i18n:getText("rl_ui_horse_singleDeath_age") or string.format(g_i18n:getText("rl_ui_horse_multipleDeaths_age"), oldAgeDeaths) end
g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText)
end
if randomDeaths > 0 then
local farmIndex = spec:getOwnerFarmId()
local farm = g_farmManager:getFarmById(farmIndex)
local msgText = ""
if animalType == 1 then msgText = randomDeaths == 1 and string.format(g_i18n:getText("rl_ui_cow_singleDeath_random"), g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) or string.format(g_i18n:getText("rl_ui_cow_multipleDeaths_random"), randomDeaths, g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) end
if animalType == 2 then msgText = randomDeaths == 1 and string.format(g_i18n:getText("rl_ui_pig_singleDeath_random"), g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) or string.format(g_i18n:getText("rl_ui_pig_multipleDeaths_random"), randomDeaths, g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) end
if animalType == 3 then msgText = randomDeaths == 1 and string.format(g_i18n:getText("rl_ui_sheep_singleDeath_random"), g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) or string.format(g_i18n:getText("rl_ui_sheep_multipleDeaths_random"), randomDeaths, g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) end
if animalType == 4 then msgText = randomDeaths == 1 and g_i18n:getText("rl_ui_chicken_singleDeath_random") or string.format(g_i18n:getText("rl_ui_chicken_multipleDeaths_random"), randomDeaths) end
if animalType == 5 then msgText = randomDeaths == 1 and string.format(g_i18n:getText("rl_ui_horse_singleDeath_random"), g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) or string.format(g_i18n:getText("rl_ui_horse_multipleDeaths_random"), randomDeaths, g_i18n:formatMoney(randomDeathsMoney, 2, true, true)) end
g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, msgText)
if randomDeathsMoney > 0 then
if self.isServer then
g_currentMission:addMoneyChange(randomDeathsMoney, farmIndex, MoneyType.SOLD_ANIMALS, true)
else
g_client:getServerConnection():sendEvent(MoneyChangeEvent.new(randomDeathsMoney, MoneyType.SOLD_ANIMALS, farmIndex))
end
if farm ~= nil then
farm:changeBalance(randomDeathsMoney, MoneyType.SOLD_ANIMALS)
end
end
end
spec.minTemp = minTemp
if randomDeaths > 0 or oldAgeDeaths > 0 or lowHealthDeaths > 0 or deadParents > 0 or totalChildren > 0 then spec:updateAnimals() end
self:raiseActive()
end
end
--LivestockTrailer.dayChanged = Utils.overwrittenFunction(LivestockTrailer.dayChanged, RealisticLivestock_LivestockTrailer.dayChanged)
================================================
FILE: src/vehicles/specializations/Rideable.lua
================================================
RL_Rideable = {}
function RL_Rideable:onLoad(save)
if save == nil then return end
local animal = Animal.loadFromXMLFile(save.xmlFile, save.key .. ".rideable.animal")
self:setCluster(animal)
end
Rideable.onLoad = Utils.appendedFunction(Rideable.onLoad, RL_Rideable.onLoad)
================================================
FILE: translations/translation_br.xml
================================================
================================================
FILE: translations/translation_cz.xml
================================================
================================================
FILE: translations/translation_da.xml
================================================
================================================
FILE: translations/translation_de.xml
================================================
================================================
FILE: translations/translation_en.xml
================================================
================================================
FILE: translations/translation_fi.xml
================================================
================================================
FILE: translations/translation_fr.xml
================================================
================================================
FILE: translations/translation_it.xml
================================================
.
================================================
FILE: translations/translation_nl.xml
================================================
================================================
FILE: translations/translation_pl.xml
================================================
================================================
FILE: translations/translation_pt.xml
================================================
Add commentMore actions
================================================
FILE: translations/translation_ru.xml
================================================
================================================
FILE: translations/translation_tr.xml
================================================
================================================
FILE: translations/translation_uk.xml
================================================
Kalderone
================================================
FILE: xml/animalNames.xml
================================================
================================================
FILE: xml/animals.xml
================================================
animals/domesticated/cow/animals.xml$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowYoung$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullYoung$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowYoung$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowMilk$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullYoung$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowYoung$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullYoung$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowYoung$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullYoung$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowYoung$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullYoung$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBullFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBuffaloYoung$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionCowProfit$l10n_animal_descriptionCowFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionCowReproduction$l10n_animal_descriptionBuffaloMilk$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBuffaloYoung$l10n_animal_descriptionBuffaloReproduction$l10n_animal_descriptionBuffaloMilk$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBuffaloReproduction$l10n_animal_descriptionBuffaloMilk$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBuffaloReproduction$l10n_animal_descriptionBuffaloMilk$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBuffaloReproduction$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullYoung$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproduction$l10n_animal_descriptionBuffaloFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBullReproductionanimals/domesticated/pigs/animals.xml$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionPigYoung$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionPigYoung$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBoarYoung$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBoarYoung$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionPigYoung$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionPigYoung$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBoarYoung$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionPigYoung$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionPigYoung$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionPigYoung$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionPigProfit$l10n_animal_descriptionPigFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionPigReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBoarYoung$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionBoarYoung$l10n_animal_descriptionBoarReproduction$l10n_animal_descriptionBoarFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionBoarReproductionanimals/domesticated/sheep/animals.xml$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionSheepProfit$l10n_animal_descriptionSheepFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionSheepReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionGoatProfit$l10n_animal_descriptionGoatFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionGoatReproduction$l10n_animal_descriptionGoatProfit$l10n_animal_descriptionGoatFeed$l10n_animal_descriptionWater$l10n_animal_descriptionSheepYoung$l10n_animal_descriptionGoatReproduction$l10n_animal_descriptionGoatProfit$l10n_animal_descriptionGoatFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionGoatReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionRamYoung$l10n_animal_descriptionRamReproduction$l10n_animal_descriptionRamFeed$l10n_animal_descriptionWater$l10n_animal_descriptionMature$l10n_animal_descriptionRamReproduction$dataS/character/animals/domesticated/horse/animals.xml$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$l10n_animal_descriptionHorseProfit$l10n_animal_descriptionHorseFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionHorseReproduction$l10n_animal_descriptionStallionProfit$l10n_animal_descriptionStallionFeed$l10n_animal_descriptionWater$l10n_animal_descriptionHorseRiding$l10n_animal_descriptionStallionReproduction$dataS/character/animals/domesticated/chicken/animals.xml$l10n_animal_descriptionChickenProfit$l10n_animal_descriptionChickenFeed$l10n_animal_descriptionChickenYoung$l10n_animal_descriptionChickenReproduction$l10n_animal_descriptionChickenProfit$l10n_animal_descriptionChickenFeed$l10n_animal_descriptionMature$l10n_animal_descriptionChickenReproduction$l10n_animal_descriptionChickenRooster$l10n_animal_descriptionChickenFeed$l10n_animal_descriptionChickenYoung$l10n_animal_descriptionChickenReproduction$l10n_animal_descriptionChickenRooster
================================================
FILE: xml/diseases.xml
================================================
================================================
FILE: xml/fillTypes.xml
================================================
================================================
FILE: xml/handTools.xml
================================================