165 lines
8.9 KiB
C++
165 lines
8.9 KiB
C++
// Lot STE, fiche ORG5-I1 : bonus d'orgasme par tirage de recompenses de stats, version minimale (V-min).
|
|
//
|
|
// Origine : comportement reconstruit d'apres LifePlay 2023.04 de Vinfamy (build 2023.04 Stable, lu par
|
|
// desassemblage du bytecode de Person.GiveOrgasmReward, Cursor.PopulateBonusRewards et BonusReward ; detail et
|
|
// sources : Docs/Migration/etat/ST/STE/STE-ORG5-SPEC.md, sections 1.1 a 1.3). Le code est ecrit a neuf ; la table
|
|
// des 20 cles, le tirage et la formule reprennent le comportement lu.
|
|
// Licence : MIT (SPDX-License-Identifier: MIT). Le travail de Vinfamy est sous licence MIT
|
|
// (Licenses/Vinfamy_License.txt, ligne 1) : utilisation, copie, modification, fusion, publication, distribution,
|
|
// sous-licence et vente permises, sans aucune garantie, a condition de conserver la mention de droit d'auteur et de
|
|
// licence. Texte de reference : Licenses/Vinfamy_License.txt.
|
|
//
|
|
// Ce que fait la V-min (STE-ORG5-SPEC.md, section 3.1) :
|
|
// - Accroche H1, sans noeud Blueprint : toutes les 0,1 s, lecture par reflexion de GI.SexOn, GI.SexPersons et, pour
|
|
// chaque personne de la scene, Person.Sex_OrgasmCounts. Une hausse du compteur = un orgasme (le compteur monte
|
|
// dans l'evenement Cum de Person) ; premier passage = memorisation sans recompense ; baisse = resynchronisation.
|
|
// Aucune lecture du journal, aucun crochet de texte. Fin de scene (SexOn faux) : la memoire des compteurs est
|
|
// videe.
|
|
// - Tempo de 0,2 s comme le RetriggerableDelay de l'auteur : un second orgasme dans les 0,2 s remplace le premier,
|
|
// seul le dernier recoit des recompenses. Les tags de position (GI.CurrentTags) sont copies a la detection.
|
|
// - Tirage exact de l'auteur : 10 cles au hasard parmi les 20, filtre Overlaps(tags de position, tags de la cle,
|
|
// vide = vrai), cle attractiontoplayer reservee aux PNJ interesses par le joueur present dans la scene, 3 au plus.
|
|
// - Application automatique de la PREMIERE recompense retenue : Person.ModifyStat(Stat, Change, false, false), la
|
|
// valeur venant de Person.QueryStat(Stat, false, false). Pas de grille ni de choix (version V-1, plus tard).
|
|
// - Le bonus d'orgasme du jeu (GiveOrgasmReward : OrgasmIrBoost, mood, attractiontoplayer) n'est pas touche ; la
|
|
// recompense s'y ajoute, comme chez l'auteur.
|
|
// - Reglage : ULPBonusRewardSettings::bDisabled, section [/Script/LifePlay.LPBonusRewardSettings] de la config Game
|
|
// du projet (Config/DefaultGame.ini) ; section absente : valeur du code (faux, fonction active). Rien n'est ecrit
|
|
// par ce code (pas de SaveConfig).
|
|
// - Journal LogLifePlay, prefixe "Bonus :" (formats de la SPEC, section 2.4) ; propriete ou fonction Blueprint
|
|
// absente ou d'un type inattendu : un avertissement par cause, abandon sans effet, jamais de plantage.
|
|
// Aucun asset n'est modifie. Regle commune ORG4 / ORG5 : un orgasme (un increment de Sex_OrgasmCounts) donne au plus
|
|
// une recompense ORG5 a la personne ; Endsex sans Cum n'incremente pas le compteur et ne declenche rien ici.
|
|
//
|
|
// Fiche ORG5-I2a (STE-ORG5-I2-SPEC.md, option a, et decisions Q-I2-1 a Q-I2-4) : affichage de la recompense appliquee
|
|
// au meme emplacement que chez l'auteur, dans le menu d'actions. A l'execution, une case "LPBonusRewardBox"
|
|
// (UVerticalBox) est ajoutee comme dernier enfant du panneau EquipMenu du widget GI.Cursor ; elle recoit une ligne
|
|
// "Bonus : prenom stat +-valeur" (blanc, police 16, contour noir 2 alpha 0,8, marge gauche 10) par recompense
|
|
// appliquee, remplacee a la recompense suivante et retiree 20 s apres son affichage ou a la fin de la scene. EquipMenu
|
|
// est porte a 500 de haut s'il est plus petit. bDisabled coupe tout (application et affichage). Cursor ou EquipMenu
|
|
// introuvable : un avertissement par cause, la recompense reste appliquee. Aucun asset modifie.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Containers/Ticker.h"
|
|
#include "Math/RandomStream.h"
|
|
#include "Misc/Optional.h"
|
|
#include "Subsystems/GameInstanceSubsystem.h"
|
|
#include "UObject/Object.h"
|
|
#include "LPBonusRewardSubsystem.generated.h"
|
|
|
|
class UPanelWidget;
|
|
class UTextBlock;
|
|
class UUserWidget;
|
|
class UVerticalBox;
|
|
|
|
/** Reglage du bonus de recompenses, lu dans la config Game du projet (objet par defaut seulement, jamais ecrit). */
|
|
UCLASS(config = Game)
|
|
class LIFEPLAY_API ULPBonusRewardSettings : public UObject
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
/** Vrai : aucune recompense tiree (equivalent de GI.DisableBonus de l'auteur) ; le bonus d'orgasme du jeu reste. */
|
|
UPROPERTY(config)
|
|
bool bDisabled = false;
|
|
};
|
|
|
|
/** Une cle de la table de l'auteur : stat (suivie de '-' si la recompense baisse la stat) et tags de position. */
|
|
struct FLPBonusStat
|
|
{
|
|
FString Key;
|
|
TArray<FString> Tags;
|
|
};
|
|
|
|
/** Orgasme detecte, en attente de la fin du tempo de 0,2 s. */
|
|
struct FLPBonusPending
|
|
{
|
|
TWeakObjectPtr<UObject> Person;
|
|
TArray<FString> Tags;
|
|
int32 Count = 0;
|
|
double FireAt = 0.0;
|
|
};
|
|
|
|
/** Ligne de recompense affichee dans EquipMenu et son echeance (minuterie de 20 s propre a la ligne). */
|
|
struct FLPBonusRewardLine
|
|
{
|
|
TWeakObjectPtr<UTextBlock> Line;
|
|
double ExpireAt = 0.0;
|
|
};
|
|
|
|
UCLASS()
|
|
class LIFEPLAY_API ULPBonusRewardSubsystem : public UGameInstanceSubsystem
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
|
virtual void Deinitialize() override;
|
|
|
|
/** Les 20 cles de l'auteur, dans l'ordre de STE-ORG5-SPEC.md section 1.2. */
|
|
static const TArray<FLPBonusStat>& Table();
|
|
|
|
/** Formule de l'auteur : signe * ceil((2 - MapRangeClamped(|X|, 0, 100, 0, 1.9)) * 10) / 10, en double. */
|
|
static double RewardChange(double X, bool bNegative);
|
|
|
|
/** Equivalent de GI.Overlaps : vrai si un element de A est dans B ; si bIfEmptyThenTrue et A ou B vide : vrai. */
|
|
static bool Overlaps(const TArray<FString>& A, const TArray<FString>& B, bool bIfEmptyThenTrue);
|
|
|
|
/**
|
|
* Tirage de l'auteur : 10 cles melangees par Rng, filtre de tags, regle attractiontoplayer, 3 au plus.
|
|
* OutStats : noms de stat sans le '-' final ; OutNegative : vrai si la cle finissait par '-'.
|
|
*/
|
|
static void Pick(const TArray<FString>& PositionTags, FRandomStream& InRng, bool bAttractionAllowed,
|
|
TArray<FString>& OutStats, TArray<bool>& OutNegative);
|
|
|
|
/** Controle de la formule sur 0, 10, 25, 50, 75, 90, 100 et le signe ; faux : OutFailure decrit l'ecart. */
|
|
static bool CheckFormula(FString& OutFailure);
|
|
|
|
private:
|
|
bool Tick(float DeltaTime);
|
|
void Poll(UObject* GI);
|
|
void FirePending();
|
|
void OnOrgasm(UObject* Person, const TArray<FString>& TagsCapturedAtDetection, int32 Count);
|
|
bool AttractionAllowed(UObject* GI, UObject* Person);
|
|
bool IsInterestedIn(UObject* GI, UObject* WhoIsInterested, UObject* WhoInterests, bool& bOutInterested);
|
|
bool QueryStat(UObject* Person, const FString& Stat, double& OutValue);
|
|
bool ModifyStat(UObject* Person, const FString& Stat, double Change);
|
|
FString PersonName(const UObject* Person);
|
|
void WarnOnce(const FString& Key, const FString& Message);
|
|
/** Vrai la premiere fois pour Key (la cause est alors notee) : l'appelant construit son message seulement si vrai. */
|
|
bool FirstWarning(const FString& Key);
|
|
|
|
// Affichage (fiche ORG5-I2a).
|
|
/** Appele par OnOrgasm apres un ModifyStat reussi (donc depuis FirePending, jamais dans la pile de Cum). */
|
|
void ShowAppliedReward(const FString& Name, const FString& Stat, double Change);
|
|
/** GI.Cursor par reflexion, en UUserWidget avec un arbre de widgets ; nul (avertissement unique) sinon. */
|
|
UUserWidget* FindCursor(UObject* GI);
|
|
/** Cursor.GetWidgetFromName("EquipMenu"), verifie par Cast en UPanelWidget ; nul (avertissement unique) sinon. */
|
|
UPanelWidget* FindEquipMenu(UUserWidget* Cursor);
|
|
/** Hauteur d'EquipMenu portee a 500 si elle est inferieure (slot de CanvasPanel seulement). */
|
|
void EnsureEquipMenuHeight(UPanelWidget* Equip);
|
|
/** Case LPBonusRewardBox retrouvee par son nom, ou creee une seule fois comme dernier enfant d'EquipMenu. */
|
|
UVerticalBox* EnsureRewardBox(UUserWidget* Cursor, UPanelWidget* Equip);
|
|
/** Retire les lignes dont l'echeance est passee (appele a chaque passage du ticker). */
|
|
void ExpireRewardLines();
|
|
/** Vide les minuteries des lignes ; bRemoveWidgets : retire aussi les lignes de l'ecran (fin de scene). */
|
|
void ClearRewardDisplay(bool bRemoveWidgets);
|
|
|
|
FTSTicker::FDelegateHandle TickHandle;
|
|
FRandomStream Rng;
|
|
/** Dernier Sex_OrgasmCounts vu par personne de la scene ; vide hors scene. */
|
|
TMap<TWeakObjectPtr<UObject>, int32> LastCounts;
|
|
TOptional<FLPBonusPending> Pending;
|
|
/** Causes d'avertissement deja journalisees (un avertissement par cause). */
|
|
TSet<FString> Warned;
|
|
/** Lignes affichees, une echeance par ligne ; videe en fin de scene et dans Deinitialize. */
|
|
TArray<FLPBonusRewardLine> RewardLines;
|
|
/** Case LPBonusRewardBox ajoutee a EquipMenu (pointeur faible : Cursor et EquipMenu peuvent etre recrees). */
|
|
TWeakObjectPtr<UVerticalBox> RewardBox;
|
|
/** GI.SexOn au dernier sondage (faux : aucune ligne n'est affichee). */
|
|
bool bInScene = false;
|
|
/** Ligne de journal "affichage dans EquipMenu" deja ecrite (une fois par session). */
|
|
bool bDisplayTargetLogged = false;
|
|
};
|