fork from Radiance

This commit is contained in:
Kazhnuz 2019-02-03 19:40:28 +01:00
parent aa18edc2f1
commit d3d1dd9be9
68 changed files with 4948 additions and 2 deletions

15
CREDITS.md Normal file
View File

@ -0,0 +1,15 @@
# Sonic Radiance Credits
## GameDesign and Programming
- Kazhnuz
## Engine used
- [Love2D](https://love2d.org/)
## Visuals
- Background and Tiles from [Shadow Shoot](https://www.spriters-resource.com/mobile/shadowshoot/) ripped by Kazhnuz and Mr. C.
- HUD elements from [FieryExplosion](https://www.spriters-resource.com/submitter/FieryExplosion/)

View File

@ -1,3 +1,17 @@
# sonic-boost
# Sonic Radiance
Un fangame basé sur Shadow Shoot
Sonic Radiance est un projet de fangame Sonic the Hedgehog en style (J)RPG avec des inspirations de Zelda-like et de T-RPG. Le but est de mélanger l'ambiance des Sonic Adventure avec le style des Sonic Advance, des Sonic Battle dans un enrobage RPG.
Le but est également de garder certains éléments "centraux" des jeux Sonic, tels que les ranks, la vitesse, l'aspect action. Cela sera fait grace à un système de combat spécial offrant au joueur des capacités d'agir plusieurs fois et évitant les trop long "temps d'attente" où le joueur ne fait que regarder le combat se dérouler. L'overworld quant à lui sera en 2D vue de haut, avec des capacités sur le terrain gardant les origines "plateforme" de la série. De plus, des mini-jeux seront présent, visant à rendre le jeu plus dynamique.
Ah, et il y aura des chao aussi.
## Synopsis
Sunlit Island est une station balnéaire populaire sur une petite île ensoleillée et agréable. Doté dune histoire riche, berceau de lancienne civilisation Sidh et sujet de projets de la compagnie Rimlight il y a 15 ans, elle peut aussi bien intéresser ceux qui ne viennent que se baigner que ceux qui cherchent à étancher leur soif de découvertes. Cependant, Sonic, Tails et Amy ont reçu un appel terrifié du maire de la ville : des badnics sont apparus partout sur lîle du jour au lendemain, et sement la terreur ! Face à ce qui semble être très certainement un coup dEggman, nos héros se sont précipités sur lîle.
Seront-ils seuls dans cet aventure ? Quel est le plan dEggman ? Quels secrets renferme l'île de Sunlit Island ? Sonic ne pourra pas seul affronter une telle menace ! Il lui faudra l'aide de ses camarades pour vaincre le savant fou et éclaircir les mystères de Sunlit Island.
## Licence
Le code du jeu est sous licence MIT. Les assets originaux sont © SEGA, et les assets et histoire sont propriété de leurs créateurs respectifs.

View File

@ -0,0 +1,61 @@
# Amy Rose
Plus grande fan autoproclamée de Sonic devenue une ami fidèle du hérisson, Amy Rose est une force de la nature en plus d'être une personne pleine de compassion et d'énergie. Cette jeune hérissonne ne désespère pas de réussir à séduire Sonic, mais est également capable de penser au plus important.
Elle est présente avec Tails et Sonic pour élucidé le mystère de l'ile de Sunlit Island
## Caractéristiques principales
- Type power
- Agit 2× par tour
- Moins forte que Knuckles, mais plus de technique et quelques capacités de soin
- Peut passer en super state? (j'ai les sprites pour, donc pourquoi pas ?)
### Sur le terrain
- Homming Dash
- Punch (détruit des éléments du décors)
## Système de combat
- **HP** :: Rank A
- **PP** :: Rank C
- **Attack** :: Rank A
- **Defense** :: Rank B
- **Technique** :: Rank C
- **Power** :: Rank B
- **Mind** :: Rank C
- **Speed** :: Rank B
### Techniques
- Rose Heal (soigne un personnage)
- Tarot Curse (maudit un ennemi)
- Amy Flash (affecte tout les ennemis avec un effet)
- Piko Tornado (G.Tornado de S. Battle)
- Gift Trap
- Hammer Attack
- Pink Tornado
- Rose Typhon
- Spin Hammer Attack
- Storming Heart (comme le Amy Flash, mais sur un ennemi + quelques dégats)

View File

@ -0,0 +1,61 @@
# Cream the Rabbit
Cream est la fille de Vanilla the Rabbit, une petite lapine calme et polie qui n'aime pas se battre. Cependant courageuse, elle n'hésite pas à venir à ses ennemis en aide. Ce n'est pas le danger qui lui fait peur, mais le fait de ne pas aimer la violence.
Elle rejoindra l'équipe après avoir été lors d'une quête annexe.
## Caractéristiques principales
- Type Technique
- Agit 2× par tour
- La plus faible en force et en pouvoir, Cream est cependant la meilleurs healeuse du jeu.
- Ne peut pas passer en super state.
### Sur le terrain
- Vol
- Attaque à distance (Chao)
## Système de combat
- **HP** :: Rank A
- **PP** :: Rank B
- **Attack** :: Rank D
- **Defense** :: Rank B
- **Technique** :: Rank A
- **Power** :: Rank C
- **Mind** :: Rank A
- **Speed** :: Rank C
### Techniques
- Chao Attack
- Heal Cheer
- Revive Cheer
- Thoughness Cheer
- Refresh Cheer
- Cure Cheer
- Demoralize
- Omochao Trap
- Chao Tornado (Chao Rush de S. Battle)
- Chao Canon

View File

@ -0,0 +1,62 @@
# Espio the Hedgehog
Ninja des Chaotix, Espio est sérieux et concentré, et se laisse peu distraire par le divertissement et autres choses du genre. Il est présent sur l'île pour enquêter sur la société Rimlight, et sur ses rapports avec GUN. Il s'intéressera particulièrement à l'affaire de la Gosth Central.
## Caractéristiques principales
- Type Speed
- Agit 2× par tour
- A pas mal de capacité pour esquiver. Basé pas mal sur les critiques.
- Ne peut pas passer en super state.
### Sur le terrain
- Homming Dash
- Attaque à distance (Kunai)
- Light Dash
## Système de combat
- **HP** :: Rank B
- **PP** :: Rank C
- **Attack** :: Rank A
- **Defense** :: Rank C
- **Technique** :: Rank B
- **Power** :: Rank C
- **Mind** :: Rank A
- **Speed** :: Rank A
### Techniques
- Shuriken
- Expl. Kunai
- Chroma Camo (augmente esquive pd 3 tours, passe Espio dans son set de Sprite "invisible")
- Leaf Swirl (tornade qui touche tout les ennemis autour et rend espio invisible 1 tour)
- Chaotic Tornado (Espio tournoi et provoque des dégats tout autour)
- Ninja Trap (Espio fait apparaitre un explosif dans le sol)
- Tornado Dash (Espio tournoi et attaque en avant sur qq cases)
- Poison Kunai
- Ninja Trick (produit un effet faibless aux ennemis autour)
- Ninja Focus (le personnage reçoit le status "FOCUS")

View File

@ -0,0 +1,69 @@
# Knuckles the Echidna
Knuckles est le gardien de la Master Emerald, ayant vécu en ermite depuis toujours sur Angel Island, l'ile flottante où la gemme mystique repose. Cet echidné prend son rôle au sérieux, et ne supporte pas que quiconque pose la main dessus. Dupé mainte fois par le passé, il est désormais plus prudent.
Cependant, un mal mystérieux s'est attaqué à la Master Emerald, la corrompant et la rendant instable. Guidée par un esprit, "Morrigan", le gardien a localisé la source de ce mal sur Sunlit Island. Serait-ce les expérimentation d'Eggman qui est la source de ce mal ? Knuckles est déterminé à élucidé ce mystère, et à sauver la Master Emerald.
## Caractéristiques principales
- Type power
- Agit 2× par tour
- Personnage le plus fort, assez tanky, mais peu resistant sur le mental
- Peut passer en super state
### Sur le terrain
- Plane & grimpe
- Punch (détruit des éléments du décors)
- L'avoir dans l'équipe offre un détecteur à objet caché.
## Système de combat
- **HP** :: Rank A
- **PP** :: Rank D
- **Attack** :: Rank S
- **Defense** :: Rank A
- **Technique** :: Rank C
- **Power** :: Rank C
- **Mind** :: Rank D
- **Speed** :: Rank C
### Techniques
- Spin Attack
- Spin Dash
- Deep Impact
- Spin Drill
- Enrage
- Ground Shaker / Hammer Punch (touche tout les ennemis sur une grande zone, utilise le Knux. Chop comme sprite)
- Uppercut
- Maximum Heat Knuckles
- Mole Bomb
- Meteor Crush
- Thunder Arrown
- Volcanic Dunk
- Guardian Heal

View File

@ -0,0 +1,67 @@
# Sonic the Hedgehog
Le renardeau suivant Sonic depuis (presque) toujours. Inventif et intelligent, Tails n'est pas autant un combattant que ses amis mais à appris à se débrouiller en combat. Il souffre d'un manque de confiance en lui, qu'il a commencé à apprendre à dépasser, mais Rome ne s'est pas faite en un jour.
Usant de la mécanique et de son intelligence, c'est un
## Caractéristiques principales
- Type Technique
- Agit 2× par tour
- Est le deuxième plus faible des perso principaux, mais peut faire pas mal d'effets secondaire et est bon avec les objets. Personnage équilibré de soutiens.
- Peut passer en super state.
### Sur le terrain
- Vol
- Arm Cannon (permet d'activer à distances des dispositif)
- Peut actionner les machines.
## Système de combat
- **HP** :: Rank C
- **PP** :: Rank A
- **Attack** :: Rank D
- **Defense** :: Rank C
- **Technique** :: Rank S
- **Power** :: Rank C
- **Mind** :: Rank A
- **Speed** :: Rank B
### Techniques
- Spin Attack :: une course tourbillonante attaquant un ennemi au sol sur trois case devant le personnage.
- Spin Dash :: une course tourbillonante chargée attaquant un ennemi au sol sur cinq case devant le personnage. Le personnage se défend automatiquement pendant le tour de charge et inflige des dégats sur les techniques direct.
- Adrénaline Rush :: Le personnage peut activer un mode Hyper sur un personnage au choix.
- Chu² Bomb :: Un piège qui traverse le terrain jusqu'à un ennemi (1 case/tour)
- Dummy Ring Bomb :: Lance trois anneau paralysant devant le personnage.
- Energy Shot :: Attaque à distance paralysante sur un ennemi en avant (5 case)
- Mecha Hook :: Lance un coup de poing mécanique tout autour de lui. Peut étourdir
- Magic Upper :: Lance un coup de poing concentré puissant sur une case devant lui.
- Tail Attack :: Un coup de queue qui attaque sur trois case devant le héros.
- Rapid Tail Attack :: Un coup de queue tournoyant attanquant tout autour plusieurs fois.
- Scan :: Permet d'obtenir les informations sur un ennemi
- Energy Laser :: Tir perforant touchant tous les ennemis en avant.

View File

@ -0,0 +1,67 @@
# Rouge the Bat
Espionne du gouvernement, intelligente, voleuse et aimant bien se jouer des autres. Aimant particulièrement taquiner Knuckles et tenter de lui voler la master emerald - bien qu'on peut se demander si le fait que ça énerve l'échidné n'est pas en soi une raison suffisante.
Elle est présente sur l'île pour faire des recherche sur le projet Gosth Island et la manière dont Eggman à obtenu des informations dessus. Elle rejoindra l'équipe avec des informations sur le projet Gosth Island, et sur le rôle de Rimlight dans toute cette histoire.
## Caractéristiques principales
- Type technique
- Agit 2× par tour
- Personnage technique assez puissant en combat, avec pas mal de capacité pour mettre des altération d'état
- Ne peut pas passer en super state
### Sur le terrain
- Vole
- Punch (détruit des éléments du décors)
- L'avoir dans l'équipe offre un détecteur à objet caché.
## Système de combat
- **HP** :: Rank C
- **PP** :: Rank A
- **Attack** :: Rank D
- **Defense** :: Rank C
- **Technique** :: Rank A
- **Power** :: Rank B
- **Mind** :: Rank C
- **Speed** :: Rank D
### Techniques
- Spin Drill
- Upper Attack (Secret Kick)
- Black Wave
- Bat Kick
- Bat Bomb
- Beauty Choc
- Charly Kick (coup de pied qui attaque tout autour)
- Distract (Generation / Chronicles)
- Jewel Storm ?
- Shriek
- Plunder
- Silent Size

View File

@ -0,0 +1,65 @@
# Shadow the Hedgehog
L'espèce vivante suprême, un hérisson sombre et peu loquace. Rarement la personne la plus sympathique avec qui parler, et plus souvent concentré sur sa mission que sur aider chaque personnage sur son passage, Shadow n'en est pas moins quelqu'un de dévoué pour protéger la planète. Il est ici afin de découvrir qui est le mystérieux "traitre" qu'il y aurait sur l'île.
## Caractéristiques principales
- Type Speed
- Agit 3× par tour
- Personnage le plus puissant à distance, et bon en vitesse. Peu de PV et
- Peut passer en super state
### Sur le terrain
- Homming Dash
- Course
- Light Dash
## Système de combat
- **HP** :: Rank D
- **PP** :: Rank B
- **Attack** :: Rank A
- **Defense** :: Rank D
- **Technique** :: Rank C
- **Power** :: Rank S
- **Mind** :: Rank D
- **Speed** :: Rank A
### Techniques
- Spin Attack
- Spin Dash
- Chaos Boost (boost temporairement ses stats)
- Chaos Control (comme le Time Stop en plus puissant)
- Chaos Blast (dégats à tout les ennemis)
- Chaos Burst (attaque autour)
- Chaos Rift (fusion avec Chaos Magic de Sonic Battle)
- Chaos Nightmare/Sphere (affect les deux côtés, ralenti les ennemis)
- Chaos Spear (attaque droit devent, perce les armures)
- Maria's Wish (regénération)
- Chaos Lance
- Light Speed Attack

View File

@ -0,0 +1,5 @@
# Sonic Radiance - Les "Chaos Breaker"
- **Ys Gradlon** est l'ancien commander des armées du GUN, disparu il y a de cela 15 ans lors de l'incident de Gosth Island.
- **??? ???** (maire de l'île) :: Le maire de l'île est un homme en apparence joviale mais stressée, constamment sur le qui-vive à cause des attaques d'Eggman. C'est lui qui a appellé nos héros pour qu'ils s'occupent d'Eggman.

View File

@ -0,0 +1,19 @@
# Sonic Radiance - Membres du peuple Sidh
## Génération Actuel
- **Taranis** est le nouveau guerrier de lEggman Empire ! Sattaquant à Sonic lorsque celui-ci tente de sauver Sunlit Metropolis, il semble défendre à sa manière les intérêt de son peuple, et en vouloir aux humains pour lexpropriation quils ont subit. Il a 14 ans. Il a une dent toute particulière contre Ankou.
- **Anrad** est une barde, sœur de Taranis, qui a refusée daider Eggman dans sa quête. Elle est gardienne des anciens chants de la civilisation Sidh. Elle suivra les héros plusieurs fois, intéressée par leurs objectif, et voulant sauver son frère des griffes d'Eggman.
- **Ankou** : Jeune garçon de 15 ans du village secondaire, Ankou est le fils de la défunte Dahut, et est l'actuel posesseur du médaillon de Dahut. Non-combattant, il a été toute sa vie la cible d'attaque de ses camarades, et est de ce fait plutôt réservé et peu loquace. Il préfère la nuit et le calme.
## Génération précédante
- **Morrigan** est l'ancienne prétresse du Stygian Rift. Ancienne ami de Athair. Elle est la grand-mère de Taranis.
- **Benelos** est un ancien chef du peuple Sidh, à l'époque où ils avaient un conseil des chef. Guerrier fier et féroce
- **Lug** est le chef actuel du peuple Sidh. Il est le fils de Morrigan, et le père de Taran.
- **Dahut** est une ancienne scientifique, membre du peuple Sidh. Elle a fait partie du projet Millenium, et est considéré comme étant la coupable de tout ce qui s'est passé durant ce projet. Ankou subit les contrecoup de cette réputation.

View File

@ -0,0 +1,67 @@
# Sonic the Hedgehog
Le hérisson le plus rapide du monde est de retour ! Orgueilleux, fier et inarrêtable, Sonic est toujours l'amoureux de la liberté et de la course qu'il a toujours été. Étonnamment plus malin qu'il le laisse paraître, le hérisson a été appellé par le maire de l'île avec Tails et Amy afin d'aider Sunlit Island dans une invasion de Badniks.
Toujours heureux de pouvoir aider les gens, il n'a pas hésité et le voilà présent sur l'île.
## Caractéristiques principales
- Type Speed
- Agit 3× par tour
- Est pas extrèmement fort mais rapide et avec une bonne précision
- Peut passer en super state.
### Sur le terrain
- Homming Dash
- Course (permet de courir sur l'eau, les tapis roulant, etc)
- Light Dash
## Système de combat
- **HP** :: Rank B
- **PP** :: Rank C
- **Attack** :: Rank B
- **Defense** :: Rank B
- **Technique** :: Rank C
- **Power** :: Rank C
- **Mind** :: Rank D
- **Speed** :: Rank S
### Techniques
- Spin Attack :: une course tourbillonante attaquant un ennemi au sol sur trois case devant le personnage.
- Spin Dash :: une course tourbillonante chargée attaquant un ennemi au sol sur cinq case devant le personnage. Le personnage se défend automatiquement pendant le tour de charge et inflige des dégats sur les techniques direct.
- Homming Attack :: Le personnage peut viser un ennemi autour de lui sur un rayon de deux case, le frappant automatiquement. Touche les ennemis aérien. Avec les niveaux, ils peut viser plus d'ennemi.
- Light Speed Attack :: Technique ultime Speed - homming attack sur tous les ennemis du terrain.
- Boost :: Le personnage attaque un ennemi sur cinq case devant lui, traverse les ennemis.
- Blue Tornado :: Le personnage balance une tornade attaquant sur un carée de 3×3 case devant lui. Les ennemis au centre se prennent plus de dégats. Supprime les bouclier.
- Sonic Wind :: Brise les bouclier de tous les ennemis devant le personnage.
- Sonic Cracker (piège) : Le personnage pose un piège qui explose au contact d'un ennemi.
- Sonic Flare : Le personnage attaque tous les ennemis autour de lui, attaque lourde.
- Sonic Wave : Le personnage envoie une vague d'énergie devant lui. Peut étourdir les ennemis.
- Speed Up : Le personnage produit l'effet "Speed Up" à un personnage au choix, lui apportant une attaque supplémentaire.
- Time Stop : Le personnage produit l'effet "figé" à un ennemi pendant 1 tours.

View File

@ -0,0 +1,84 @@
# Sonic Radiance - Locations
Le jeu entier se passe sur deux îles : Sunlit Island et Gosth Island.
Le jeu étant inspiré en grande partie des jeux type Shadow Shoot et autre jeux de l'époque 1998-2006 du hérisson, le jeu fera un hommage au peu connu Shadow Shoot en reprenant une partie de ces niveaux comme emplacement sur Sunlit Island, et reprenant le thème global ("Pierre Précieuse + Nom de Lieu") pour nommer une partie des zones.
Certains noms seront un peu modifié afin de ressembler plus aux thèmes du jeu.
## Les cinq grands espaces de l'île
### La ville :: Diamound Metropolis (au centre)
- **Diamound Metropolis** :: La plus grande ville de l'île, Diamound Metropolis est une ville touristique puissante et impressionnante. Ce niveau est inspiré du niveau "Diamound Highway" de Shadow Shoot.
- **Diamound Highway** :: La "Diamound Highway" est la voie rapide amenant de la ville au centre industrielle de Rimlight. Il s'agit en quelque sorte d'une "extension" de cette première
- **Rimlight HQ** :: Le centre de commande de l'entreprise Rimlight sur l'île, qui contrôle tout les générateurs Chaotique de l'île.
- **Agate Park** :: Un park/casino immense, située dnas la Diamound Metropolis. Un endroit utile pour gagner… ou bien pour tout perdre ?
- **Obsidian Station** :: Au dessus de l'île, en orbite géostationnaire, passe régulièrement cette station spatiale, station principale du projet ERINYE.
### La zone aride/sèche (ouest)
- **Moonstone Valley** :: Une ancienne vallée, située à l'ouest de la grande ville. Cette vallée plutôt aride est ce qui offre l'accès à plusieurs endroits connus.
- **Amber Canyon** :: Un canyon ambré, uniquement masqué par le *Sidh Village*. C'est ici que se trouve l'un des dongeons du peuple Sidh, *Ancient Scripture*.
- **Sidh Village** :: Le village historique des Sidh, juste avant l'Amber Canyon.
- **Zircon Desert** :: Tout une zone désertique, située au Nord-Ouest de l'île, ou se trouve quelques secrets…
- **Justice Altar** :: Les anciennes épreuves de la justice du peuple Sidh.
- **Moonstone Graveyard** :: Un ancien cimetierre, où se trouve de nombreuses choses étranges et quelques fantomes…
### La zone montagneuse (Nord)
- **Pearl Mountain** :: Une montagne neigeuse, et au sommet de laquelle se trouve d'ancienne installation du *Projet Millenium*.
- **Garnet Crater** :: L'intérieur de la *Pearl Mountain* est un cratère volcanique étrangement plein de lave… serait-ce les expériementation d'Eggman ou celles d'il y a 10 ans qui ont fait revenir son activité ?
- **Thermal Station** :: Une base thermique à l'intérieur des volcans.
- **Force Cave** :: Les anciennes épreuves de la force du peuple Sidh.
- **Crystal Utopia** :: Un immense complexe technologique situé au sommet de la Pearl Mountain. Aucune information n'est disponible dessus.
### La zone forestière (est + nord-est)
- **Calcite Hills** :: Le niveau type "GHZ", les Calcite Hill sont des collines verdoyantes à l'est de la grande ville. Il s'agit d'un espace souvent visité par les habitant, pour accéder soit à la forêt soit aux ruines.
- **Jadeite Forest** :: Jadeite Forest est une forêt s'accédant depuis les Calcites Hills, située au Nord-Ouest de la ville (niveau provenant de Shadow Shoot). Cette forêt tempérée est un espace en retrait souvent apprécié des gens voulant de la tranquilité.
- **Temperence Grounds** :: Les anciennes épreuves de la tempérence du peuple Sidh.
- **Graphit Base** :: Une des bases d'Eggman sur l'île, abandonnée en première vue...
- **Ametist Temples** :: D'ancien temples du peuples Sidh, abandonnés depuis longtemps. Ils contiennent des informations sur une mystérieuse force nommée le Styx (Niveau situé au sud des Calcites Hills, inspiré de l'Amethist Castle de Shadow Shoot)
### La plage et l'océan (sud)
- **Olivine Coast** : Une côte mélangeant palmier et sable fin. Le premier niveau de tout Sonic qui se respecte.
- **Olivine Groove** : Une petite zone boisée située tout à l'est de l'île. C'est souvent un lieu de cachète et de jeu pour les enfants du *Merchant Village*.
- **Wisdom Trials** :: Les anciennes épreuves de la sagesse du peuple Sidh. Sous la mer.
- **Carnelian Bridge** :: Un immense pont militarisé reliant les deux île de Sunlit Island et Gosth Island. Au milieu se trouve une base de GUN éponyme, représentant la limite de où les gens peuvent aller sans autorisation spécial du gouvernement de l'île. (niveau repris de Shadow Shoot)
- **Peridot Archive** :: Une archive sous-marine (inspirée du Peridot Tunnel de Shadow Shoot) du G.U.N. se trouvant en dessous de la base militaire de Carnelian Bridge.
## 2. Gosth Island
Le but des niveaux complémentaires va être de contenir les tropes qui ne sont pas présents dans les niveaux de bases du jeu. Il y aura 9 niveaux, pour atteindre un total de 16 niveaux (4×4 pour le mode "tournois).
- **Gosth Central/Gadget** :: Le donjon principal de l'île. Une immense centrale chaotique, pleine de phénomènes étranges lié à l'énergie crée. Ce à quoi Eggman s'intéresse beaucoup sur cette île.
- **Chaos Core** :: Le Centre de la centrale. Danger. Énergie sauvage qui traverse partout.
- **Silicon Matrix** :: Le système numérique principale de l'île, où se trouvent toute les véritables informations sur le déroulement du *projet Millenium*.
- **Stygian Realm** :: L'univers-bulle d'où provient la Stygian Energy. Espace instable et peu compréhensible.

View File

@ -0,0 +1,19 @@
## Levels in Sonic Boost game mode
## Correspondance de niveaux (mode classique)
- Niveau 1 : **Jadeite Forest** / **Calcite Hill**
- Niveau 2 : **Diamound Metropolis** / **Agate Park**
- Niveau 3 : **Peridot Archive** / **Zircon Desert**
- Niveau 4 : **Pearl Mountain** / **Carnelian Bridge**
- Niveau 5 : **Graphit Base** / **Moonstone Ruins**
- Niveau 6 : **Garnet Crater** / **Silicon Matrix**
- Niveau 7 : **Obsidian Station** / **Ametist Temples**
- Niveau Bonus : **Crystal Utopia**
## Coupes (mode tournoi)
- Chao Cup : Calcite Hill - ????? - ????? - ?????
- Ring Cup : ????? - ????? - ????? - ?????
- Emerald Cup : ????? - ????? - ????? - ?????
- Master Cup : ????? - ????? - ????? - **Crystal Cosmos**

View File

@ -0,0 +1,21 @@
# Sonic Radiance - Main Story - Act 1 : Des robots partouts
## Synopsis
Sonic, Tails et Amy ont été appellée sur l'ile de Sunlit Island par le maire de l'ile. L'ile fait en effet face à des attaques ponctuelles de Badnics, mais sans qu'Eggman ait fait la moindre annonce ou menace. Les trois héros arrivent donc sur l'île, et vont d'abord devoir explorer la ville principale (Sunlit Metropolis) pour trouver le maire, et avoir leur mission.
Il apprendront que les badnics attaquent en ce moment un petit village touristique du sud de l'île, le Merchant Village. Les trois héros devront se diriger dans ce village, afin d'aller sauver les habitants.
Pour cela, ils vont devoir tout d'abord traverser les plages, qui sont infestées de badnics. Ce sera l'occasion d'avoir les premiers combats expliquant le système de combat. Traversant la plage jusqu'au village, ils vont trouver un village attaqué par les robots d'Eggman, qui cherchent *un mystérieux médaillon*. Le garçon qui le possède, ainsi que certains autres jeunes du village, vont fuir jusqu'à la crique à l'autre bout de la plage. Les héros devront aller les chercher.
Le groupe de cinq ados se trouve dans le premier "mini-donjon". Les personnages y utiliseront leur première capacité, le saut, montrant le mélange zelda+plateformer de l'overworld et des donjons. A la fin de ce mini-donjon, les personnages découvriront le premier boss, un Egg-Robo. Cependant, après l'avoir vaincu, ils ne réussiront à sauver que quatre des cinq enfant. Ankou, *l'enfant au médaillon* se fera capturer par un autre EggRobo, qui l'aménera à un endroit inconnu.
Les héros devront alors retrouver le jeune Ankou, mais découvriront que cela ne sera pas forcément facile...
## Éléments importants
- 2 ville : Sunlit Metropolis (peut-être changer le nom pour faire référence aux niveaux tirés de Shadow Boost ?)
- 1 zone de l'overworld (la plage)
- 1 donjon (le bosquet)

View File

@ -0,0 +1,11 @@
# Sonic Radiance - Lore
Le Lore de Sonic Radiance contiendra principalement deux éléments : des éléments qui sont spécifiques au jeu, à savoir des éléments sur l'histoire du jeu et la backstory de l'île ou se produit du jeu, et des éléments qui seront spécifique à la vision du monde de Sonic qui est développé dans le jeu.
Le but de ce fichier est de présenter quelques uns des éléments de base qui constituent ce lore :
- **Un jeu centré autour d'un lieu en particulier** : Comme SA (et SA2 dans une moindre mesure ?), le jeu se situe autour d'une histoire bien particulière qui aura des répercutions sur le "général". L'histoire sera situé autour de l'île de Sunlit Island, et le lore sera donc adapté autour de cet histoire.
- **Ambiance typé 2000** : Ce jeu est également un hommage à toute une époque. L'ambiance sera donc à la fois typé Adventure, mais surtout un hommage à tous les "petit Sonic" sorti à cette époque (Advance, Battle, et les jeux mobiles). En découlera une ambiance qui sera unique, et des références à des plot-lines oubliées depuis un moment. Cependant, cela ne veut pas dire que *rien* des jeux récent ne sera adapté ! Certains éléments considérés comme pertinant pour l'histoire ou le gameplay seront présent. Idem pour les éléments du "monde classic", certains seront adapté.
- **Two-World** : Ce jeu utilise le two-world, parce qu'une des sous-intrigue est fondée dessus.

View File

@ -0,0 +1,69 @@
# Backstory : Le projet Gosth Island
## Le projet Millenium, le rêve dune Utopie
Le projet Gosth Island était lun des projets fondé par le « projet Millenium », un projet qui visait à construire partout sur la planète des « villes du futur ». Parmi les autres villes fondées par ce projet, il y a Monopole, Metal City et Grand Metropolis. Ce projet était financé par Rimlight et les Fédérations Unis, et tout un tas dautres acteurs privés. Les Fédérations Unis avaient pour but doffrir un monde meilleurs à tout les habitants de la planète, et empêcher à nouveau les guerres dintervenir. Et le but de ces villes étaient dillustrer la meilleure vie quils auraient tous.
Ils se sont intéressé à Gosth Island, parce quils estimaient que cette nouvelle énergie présente sur lîle serait un moyen de créer une ville comme jamais vu auparavant, qui dépasserait toute les autres, surtout sils réussissaient à faire venir avec eux le peuple qui justement « contrôlait » pour GUN la faille dont provenait lénergie.
## Lachat de Gosth Island
Rimlight sest donc rendu chez les Sidh et leur a proposé un deal : la coalition pour le projet pourrait travailler sur le Stygian Rift, à condition doffrir une compensation financière à tout les membres du peuple qui accepteraient. Ils pourraient alors soit aller vers un autre de leur village, le Sidh Village, soit participer à la construction de la cité du futur et des installations scientifiques.
Le conseil réfléchi plusieurs jours, dans des débats extrêmement tendu. Sur les chefs, une partie étaient pour accepter loffre, une partie contre. Contre toute attente, cest la fille de lun des membres du conseil et une apprentie-prêtresse qui fit basculer la balance en rejoignant elle-même le projet, voulant participer à cette création dune utopie. Son argumentation fit que quatre des membres du conseil furent pour la vente de lîle. Trois encore refusaient :
- Benelos, un grand guerrier, qui estimait Ys Gradlon indigne de confiance, et qui refusait de vendre un territoire sacré à des humains. Il estimait que même pour une vie meilleurs, cétait une insulte envers leur membre, et quil fallait chercher ailleurs les ressources dont ils manquaient cruellement.
- Morrigan, la prêtresse de la Stygian Rift estimait quutiliser comme une source dénergie quelque chose daussi instable les faisait courir à la catastrophe. Lénergie Stygienne était par nature difficile à contrôler, et son aspect corrupteur aurait sans doute des effects catastrophique sur lutopie.
Cependant, Dahut réussi à convaincre les autres que les risques étaient minimes par rapport à leur situation actuelle : Elle mis en valeur le faible tôt dalphabétisation de leur population, la mortalité infantile très élevée. La jeune femme estimait quils réussiraient avec leur force à préserver leur civilisation dans la modernité, comme la plupars des autres royaumes mobiens. Elle voulait utiliser la modernité comme un moyen pour les Sidh de continuer à exister, parce quelle estimait que simplement lignorer serait juste faire une politique de lautruche jusquà ce quil serait trop tard pour eux et se ferait engloutir parce quils nauraient plus le choix.
- Lug, le plus jeune des membres du conseil, qui estimait que les contreparties étaient très faible, en vue du fait qu'avant l'arrivée des humains sur l'île, ils étaient maître de toutes l'ile.
Pendant plusieurs jours, les débats devinrent de plus en plus violent, la situation étant bloqué.
Les débats montèrent en intensité jusquà ce que Morrigan et Benelos furent radié du conseil pour les empêcher de continuer à poser leur véto, et exclu avec ceux qui les suivaient de la compensation financière. Ce fut le père de Dahut qui signa le traité avec les Fédérations Unis, ouvrant le début du projet Gosth Island. Il émigra dans le Feral Village pour assurer lordre dans son peuple, tandis que les autres chefs décidèrent de rester avec plusieurs Sidh pour participer aux travaux. Ceux qui étaient contre les travaux durent partir aussi, mais sans avoir le droit à une meilleure vie.
## Des débuts prometteurs mais sous les tensions
Les travaux commencèrent avec pas mal de succès dans les premiers temps. Ils réussirent à extraire et convertir lénergie de la faille, et la centrale fut rapidement opérationnelle. Dahut gagna rapidement un rôle important, puisquelle était celle qui connaissait le mieux le fonctionnement de la faille. Lénergie était plus forte que tout ce que Rimlight avait prévu, et ils comprirent quils pourraient allimenter toute la région avec. Lavancement de la ville se passait bien, et la construction dun dôme capable de recréer des climats impressionnait les masses.
Dans la population de la ville, ce choix fut très décrier, et une partie du conseil du s'en aller de la ville principal, ou ne restèrent que Lug, Benelos et Morrigan pour diriger la ville. Un nouveau village fut entièrement financé par Rimlight pour celles et ceux qui voudraient collaborer avec eux.
Ils commencèrent à construire une route vers le Feral Village, pour les alimenter aussi, après des négociations du père de Dahut qui estimait que puisquils allaient réussir plus, le peuple Sidh méritait aussi plus. Ce fut accepter, et le Dahut commença à jouir dune réputation dexcellent négociateur. Mais une négociation était bien plus difficile, celle avec les membres de l'ancien village. Ceux-ci avaient cristalisé lopposition, pour quelle continue à exister. Ils continuèrent dadresser des critiques au projet, et commencèrent a tenter dalerter la population générale des deux villages sur la situation de leur peuple.
Ys Gradlon, commandant du GUN, rejoint alors le projet pour pacifier la situation. Sa stratégie consistait à essayer de diviser le peuple Sidh entre les pour et les contre, pour pouvoir isoler les contre et éviter leur sentiment de sétendre.
Cette situation dura plusieurs mois, dans un statut quo où dun côté le projet avançait bien, mais où les critiques étaient toujours plus présente.
Cependant, un incident eut lieu. Un jeune guerrier, Albius, sétait infiltré dans la centrale pour faire un coup déclat. Il fut blessé par balle et plongé dans la faille, directement dans le Stygian Realm. Personne ne sut si le soldat avait fait exprès de jeter le jeune adulte blessé dans la faille ou si c'était un accident. Mais cétait un acte dune violence incroyable pour le peuple Sidh, ce qui provoqua des troubles, et surtout la colère de Morrigan et de Benelos.
La guerre avait été déclarée.
## La bataille de Gosth Island
Une nuit, Benelos attaqua lile avec une armée de soldat. Leur but était simple : reprendre leur territoire. La bataille fit rage pendant des heures, et Benelos alla sattaquer directement à Ys Gradlon, qui avait décidé de garder la faille. Le combat entre les deux fut violent, Ys Gradlon compensant sa faiblesse physique face au mobien bien plus fort par la supériorité de ses armes. En dehors, les soldats du G.U.N. et les guerriers Sidh se battait avec une férocité jamais vue.
La bataille fut remportée par GUN, et les guerriers Sidh furent fait prisonnier. Bénélos cependant neut pas cette chance, sombrant lors du combat dans la faille. Ys Gradlon tenta de sauver son adversaire, voulant lui offrir un procès équitable, mais en vain. Quant à Morrigan, elle tomba au combat, laissant la faille sans prêtre qualifié pour la première fois depuis des siècles.
Le peuple Sidh ne retrouva jamais des forces de batailles pour affronter GUN. Cependant, cet événement eut des conséquences très néfastes sur la stabilité de Gradlon, qui sombra petit à petit dans la paranoïa.
## La démesure de Gradlon
Ys Gradlon déclara que le projet devait passer entièrement sous contrôle de GUN. Si dans les faits toutes les sociétés qui travaillaient sur le projet pûrent continuer, lile se militarisa de plus en plus. Les habitants du peuple Sidh sur lile furent déplacé dans des dortoirs, afin de sassurer de labsence de traître. Il commença à sintéresser également aux application militaire du projet. Il considérait lattaque des Sidh comme une trahison envers la Fédération même, et commença a craindre plus dennemi intérieurs. Faisant des cauchemars chaque nuit, devenant de plus en plus méfiant, il garda le même but quavant protéger les Fédérations Unis et ses habitants mais devint prêt à le faire à tout prix.
Il fit étudier le Stygian Realm, et la manière dont la faille pouvait happer êtres vivants, songeant aux possibilités de lutiliser pour faire disparaître des « ennemis des United Federation » (cest-à-dire les forces militaires opposées à lunification des états dans la Fédération, les grands bandits, et les groupes terroristes). Il voyait cette dimension comme « la prison ultime », puisque ce serait une prison ou il serait impossible de sévader, à cause de la corruption provoqué par le Styx. Il commença à théoriser qu'en fin de compte, les mobiens n'étaient rien de plus que des aliens proche à envahir la Terre.
Ce fut à ce moment là que Dahut commença a se rebeller. Elle refusa de coopérer, estimant quune telle prison était contraire à ce que la Fédération Unie et leur projet tenaient. Ys Gradlon sénerva, et fit emprisonner Dahut. Le Syphon dEnergie du Gosth Core fut maintenu par une plus petite équipe, dont une partie des membres était dévoué à garantir la stability du Stygian Rift.
Ys Gradlon fit construit une armée de satellite, les Erinyie, capable de capter lénergie de la centrale et de se la transmettre, afin de créer une armada de satellite capable de toucher nimporte quel point de la terre. Les forces automatisé de construction furent lancé en orbite, et les Fédérations Unis seraient doté de la capacité de frapper partout sur la Terre, à nimporte quel moment.
Les Fédérations Unis seraient désormais protégée de toute menace.
## La fin du projet.
Cependant, Dahut fini par réussir à sévader. Elle vit ce quétait devenu le projet : en place de ville du futur, dutopie pour lavenir, il ny aurait quune arme ultime. Elle décida alors de détruire toute lœuvre de sa vie ce pour quoi elle avait été même prête à trahir sa famille et possiblement se tuer avec : Saboter la Gosth Central, pour mettre fin définitivement au projet.
Les humains la retrouvèrent en train de saboter plusieurs parties de la centrale, brisant tuyaux, détruisant machines de contrôles. Avait-elle pour but de faire exploser la centrale ? Elle fut attrapé après son œuvre, et exécutée après son refus de dire comment réparer ce quelle avait fait. Cependant, Gradlon refusa dévacuer lîle, et ordonna de couper la centrale le temps de réparer les dégats.
Ce fut là que lincident se produisit. Dahut avait réussi à placer au fond du Stygian Altar, dans une petite crypte quaucun humain ne connaissait, un appareil de sa création. Un petit appareil qui capitait lénergie et la renvoyait, de sorte à faire saturer toute lénergie Stygienne. Comme un immense court-circuit dénergie, à lintérieur même de la faille. Cela provoquerait le même phénomène de « happe » qui avait touché Bénélos et quutilisait Gradlon, mais à léchelle de lile entière. Les sabotages navaient été quune couverture à sa véritable action, et la machine navait pas été retrouvée.
La saturation dénergie se produisit donc, et, en une fraction de seconde, il ne resta plus un seul humain ou sidh sur lîle de Gosth Island. De nombreux dégats furent produits sur les mini-générateurs Chaotique de Sunlit Island également. Face à la disparition de leur commandant, et à la perte de toutes les personnes savant faire fonctionner lîle, elle fut désaffectée, et lîle passa sous surveillance de GUN pendant 15 ans.
Ce fut la fin du projet Millenium.

View File

@ -0,0 +1,61 @@
# Sonic Radiance - Peuple Sidth
Le peuple Sidh est un peuple de Mobien hétéroclite qui vit sur l'île de Sunlit Island. Ayant autrefois occupé toute l'île, ainsi que la petite île au large de Gosth Island, ils ne vivent aujourd'hui que dans le XXXXXX Village, suité dans les plaines de XXXXX.
Plus qu'un peuple basé sur une race (genre les échidnés), ils sont plus réunis par une culture. Basée sur une culture celtique (notamment les mythologies Bretonne, Irlandaise, Galloise et Ecossaise), les personnages de ce peuple ne sont pas nommés suivant la base normale de Sonic (des mots anglais), mais selon des termes de la culture visée (ici des dieux ou mots celtes), un peu comme Tikal et Pachacamac ont des noms basé sur des temples améridiens.
Leur rôle dans l'intrigue sera un peu ambigüe, notamment à travers les personnages de *Taranis*, *Ankou* ou *Anrad*. Leur nom vient du nom du *Sidh*, royaume des morts chez les Celtes.
## Culture et mode de vie
Vivant dans leur village, dans les plaines et juste avant le XXXX (qu'ils protègent des intrus). Ils vivent principalement en autarcie, et ont une grande méfiance pour les humains. Ils accepteront même de s'allier avec Eggman en échange de vivre et de soutiens financier (estimant que la guerre contre Eggman est une affaire d'humain).
Aujourd'hui, ils protègent surtout le Super Warp Ring qui se trouve dans les cavernes au fond de leur vallée.
Panthéïstes, ils croient en la *Grande Mère*, qui est une déesse de la nature qui est présente en chaque endroit en chaque instant. De ce fait, ils vénèrent tout ce qui est surnaturel, et proche du Chaos, la principale des forces naturelles. En cela, ils ont longtemps vénéré le Stygian Rift situé sur l'île de Gosth Island, qu'ils alimentaient en sacrifice jusqu'à ce qu'ils soient exproprié de l'île de Gosth Island, lors de la construction de la Gosth Central
La "faille noire" comme ils l'appelle reste aujourd'hui encore ce qu'ils vénèrent le plus, et cette expropriation est source de leur colère contre les Fédérations Unis.
Leur chef actuel est Lug, père de Taranis et Anrad, qui est prêt à tout ce qu'il faudra pour aider ses enfants et surtout son peuple.
### Le second village
Il existe un second village, se trouvant au niveau des plages du sud. Ce village est constitué des Sidh ayant désiré participé dans le projet Gosth Island et n'ayant pas péri lors de l'incident.
Les villageois du second village (surnommé le village de *Bhàin*) vivent principalement du revenu du tourisme, et sont assez pauvre mais restent attaché à leur indépendance et à leur mode de vie.
Cependant, ils sont beaucoup moins attaché au fait de rester séparer des humains, et sont considéré par leur pairs du village principal comme des traitres.
## Histoire du peuple Sidh
### Les premiers Sidh
Le peuple Sidh sont à lorigine un groupe de mobien hétéroclite qui sest installé sur lîle, qui était déserte à lorigine, il y a très longtemps par le Super Warp Ring. Suivant un petit groupe de penseurs, les Sages, ils fondèrent une société, visant à vivre suivant leur vertu et leurs idéaux. Fondée sur la pensée et la recherche du bien absolu, ils ont fondé une civilisation surtout centrée sur la discussion et la présence de ces Sages qui devaient conduire les débats et faire évoluer la science.
Tous les sages et les chefs se devaient de passer quatre épreuve, liées aux quatre vertues cardinales (le courage, la tempérence, la justice et la sagesse). Leur but était de créer une civilisation d'être qui auraient atteint l'état de perfection morale. Cependant, dans leur vision, ils avaient également un aspect "pédant". S'ils voulaient aider les autres, ils avaient tendance à voir de haut les peuples "moins intelligents"
Petit à petit, ils sont devenus une civilisation bénévole et qui a eut de grande avancées techniques, à la manière des Chozos de Metroid. Ils découvrirent sur la petite île de Gosth Island au large de leur île principale la *Chaos Vein*, qu'ils commencèrent à étudier. C'est eux qui sont à l'origine de tous les temples et technologies anciennes qui se trouvent sur l'île. Leur but était d'utiliser la technologie de la faille pour améliorer la santé de leur peuple, ainsi que de facilité la vie des différents peuples.
### La chute
Cependant, ce qui a été la source de leur brillance fut celle aussi de leur fin, à cause de la corruption de la Chaos Vein. En effet, vers la fin de l'île, un groupe de sage commença un nouveau projet. Leur but était de créer un pouvoir qui vaincrait la malchance et l'infortune, la maladie et la peur. Leur but n'était pas de "vaincre la mort" ou d'autres choses du genre, mais de créer une énergie qui apporterait à tous santé et bonne fortune.
Mais une chose leur manquait. La puissance. La Chaos Vein leur apportait énormément de puissance, mais ce n'était pas suffisant pour leur plan. Cela provoqua chez les sages qui travaillait sur le plan une dispute. Que faire ? Des tas de possibilités existaient. Faire une arme provoquant méfortune, et aller chercher d'autres failles ? Abandonner le projet et se contenter des améliorations déjà présente ? Leur choix fut tout autre.
Les Sidh tentèrent de faire évoluer la Chaos Vein afin de pouvoir avoir plus d'énergie. Utilisant un dispositif pour centrer l'énerige de la faille, cette machine s'emballa, provoquant une catastrophe. Cela provoqua l'évolution du Chaos en une autre force, le Styx. Cette énergie corrompu commença a tout envahir, et la faille commença a happer des êtres vivants.
### La période sombre et la reconstruction
Le peuple Sidh, ayant perdu une grande partie de son peuple et de sa civilisation, ne survécu pas en tant que civilisation. Il s'effondra progressivement, et bientôt ne resta de l'ancienne époque qu'un souvenir lointant, celui de l'époque ou le peuple Sidh était "proche des dieux".
Ils commencèrent à craindre et à vénérer la Chaos Vein, désormais devenu le Stygian Rift. Entendant ses murmures, ils le voyaient comme une émanation maléfique de la puissance naturelle. Ils y ont longtemps conduit des sacrifices. Cependant, cela ne fit qu'augmenter la voracité et l'instabilité de la faille, jusqu'à ce qu'ils approchent de l'extermination à cause de cette voracité.
Les quelques sages qui restaient continuèrent à faire les épreuves et à conduire la civilisation, mais ne connaissaient que de moins en moins les origines de leur culture. Ils s'isolèrent de plus en plus du monde, conscient d'avoir été à l'origine de la naissance d'un danger, et effrayé par ce danger. Ce n'était pas qu'ils ne comprenaient pas ce danger ou qu'ils étaient stupide : ils étaient effrayé, et n'avaient plus le choix.
Ce fut lorsque la civilisation approcha encore de la chute qu'une des sages décida de tenter autre chose *Dana*, toute sa vie, elle étudia et appris de la faille, avec ses collègues, jusqu'à aller affronter la faille pour la calmer. Elle réussi à apaiser la faille, devenant la première des pretresses de la faille.
Petit à petit, les sages et les rituels disparurent, remplacé petit à petit par un conseil, qui prenait les décisions avec le reste du peuple. Le peuple continua à exister, et évolua perdant son accès à ses technologies. L'île de Gosth Island devint une île sacrée. Les prêtres de la faille continuèrent à exister, et cet état de fait évolua, jusqu'à ce qu'arrive le *projet Millienium* il y a quinze ans.
En effet, l'incident du projet Millienium provoqua le décès d'une partie du conseil, et la perte de Gosth Island qui devint une île interdite, protégée militairement par les *GUN*. Il ne resta du conseil plus qu'un des chefs (Lug, le chef actuel), une chef existant à l'état spectral (Morrigan) et une fille de chef supposée vivante mais disparue depuis l'incident de Gosth Island. (Dahut)
Le peuple vit aujourdhui divisé, dans une certaine précarité, et une partie sest tourné récemment vers Eggman, lui offrant des connaissance sur le Stygian Rift en échange de nourriture, de médicament et de vengeance pour certains. Les survivants de l'incident ayant été membres du projet furent bannis, et fondèrent un petit village sur la côte, vivant désormais des revenus touristiques.

View File

@ -0,0 +1,41 @@
# La Terre
La "Terre" de Sonic Radiance est une terre alternative à la notre, ou les pays sont principalement remplacé par leurs Erzats de Sonic Radiance, et où la forme des continent est légèrement différent.
Cette Terre possède comme principale puissance politique les *Fédérations Unis*, et comme plus grande force militaire les *Guardian Units of Nations* (ou GUN).
## Les Fédérations Unis
Les Fédérations Unis sont la république parlementaire avec un régime présidentiel qui couvre une grande partie de la planète Terre. Plus puissante nation de la planète, elle a subit plusieurs crise depuis le retour du Docteur *Ivo Robotnik* sur Terre lors de l'incident de Christmas Island.
Elles peuvent être vu comme quelque chose entres les Nations-Unis et les États-Unis. Le but de cet état est de garantir un développement tout en respectant les libertés des pays qui en font partie (et qui conserve une certaine indépendance). L'île de Sunlit Island est sous contrôle des Fédérations Unis, avec comme représentant le maire de la ville de *Sunlit Metropolis*.
Leur technologie est majoritairement contemporaire, mais a réussi de grande avancée depuis 50 ans à l'aide des technologies chaotique, en grande partie découvertes par le professeur Gérald
Dans le jeu, les Fédérations Unis, n'ont pas une présence très forte dans le jeu, en tout cas pas directement.
## Les Gardian Units of Nations
Les Gardian Units of Nations (ou G.U.N. parfois orthographié en GUN) sont la principale force militaire des Fédérations Unis, et ont comme rôle de protéger à la fois les Fédérations Unis, et à la fois les pays alliés. Cette armée est dirigé par un Commandant.
L'armée est actuellement en grande partie dédiée à affronter le Dr. Ivo Robotnik, qui vise à conquérir le monde, mais narrive pas à le localiser et à lancer des attaques précises. La capacité à disparaître de ce savant étant déconcertante, et ses moyens encore inconnus. L'inefficacité de GUN à vaincre les menances originaire du monde des animaux est la source d'une méfiance chez certaines personnes.
## Rimlight, les Chaos Central et les Chaos Vein
La société Rimlight est lun des éléments scénaristique important du jeu. Holding co-détenu par des actionnaires privés et par le GUN, la société recherche dans le domaine des énergies propres, et plus précisément dans le domaine des énergies chaotiques.
Ils utilisent notamment pour cela les thèses de Gerald Robotnik sur ce type dénergie, quand il était chercheur à luniversité de Central City, thèses qui avaient conduit Gerald à faire des recherches sur les civilisations anciennes échidnés, pour mieux comprendre leur rapport à lénergie chaotique (doù les découvertes sur Chaos, la prophétie échidné, Gizoïd…) et le fait quil fut choisit comme chef de léquipe du projet Shadow. Ils sont les producteurs des Chaos Drive, et des Chaos Central.
### Les Chaos Central
Les Chaos Central sont une source dénergie de plus en plus présente sur toute la planète. Construite sur des zones nommées les Veines Chaotiques (Chaos Vein) et sont utilisées depuis environs vingt ans (pour le marché publique) à vingt-cinq ans (pour les Guardian Units of Nations). Lénergie produite par les Chaos Central revet deux forme : léléctricité, ou les Chaos Drives, des batteries chaotiques extrêmement durable utilisée notamment par les mécha des Guardian Units of Nations.
Lintérieur dune centrale chaotique est cependant un endroit dangereux à cause de linfluence de lénergie chaotique et de sa transformation en électricité ou en *Chaos Shard* pour les Chaos Drive, et est réservé à des connaisseurs de lénergie chaotique. Le cœur dune centrale chaotique, nommé le *Chaos Core*, est un endroit ou lénergie circule suivant des patterns très précis.
Cest la deuxième source dénergie la plus utilisée, après lénergie thermique liquide produite par la société HexaECO, utilisée depuis à peu près la même période.
### Les Chaos Vein
Les Chaos Vein (Veines Chaotiques) sont la source de lénergie utilisée dans les Chaos Central du conglomérat Rimlight. Il sagit de zones démission spontanée dÉnergie Chaotique qui existent à quelques endroits de la planète, relativement rares mais trouvable sur tout les continents. Même si ces failles sont rien face à des Chaos Emerald, il est possible de produire de une énergie propre et durable à laide de ces failles. Quand elles ne sont pas sur-exploité (comme ce qui est arrivé dans un incident avec lune des premières centrales chaotiques), ces failles sont contrairement au Chaos Emerald particulièrement docile à utiliser.
Les Chaos Vein sont des failles vers des univers-bulles qui leur sont propre (des sortes de Special Zone à petite échelle, mais qui ne contiennent pas de Chaos Emerald) et qui est la source de cette énergie. Cest ce qui fait laspect durable et stable à la fois de cette énergie : seul très peu dénergie séchappe de la veine, mais la reserve quil y a derrière est largement au dessus de toute la consommation humaine planétaire depuis les débuts de la révolution industrielle, au bordure de limmesurable.

View File

@ -0,0 +1,29 @@
# Sonic Radiance - Les deux mondes
Sonic Radiance se base au niveau de son univers sur le concept des deux mondes (à la Sonic X) : Deux univers coexistent, et les personnages peuvent passer de l'un à l'autre. Ces deux mondes sont ceux de la Terre, une planète organisée et peuplée surtout par les humains, et un monde sans nom nommé le "monde des animaux", peuplé surtout par des animaux anthropomorphiques intelligents.
Le passage d'un monde à l'autre est conduit par l'existence de *Super Warp Ring*, des immenses anneaux qui ont le pouvoir d'être des passage entre les deux mondes. Ils étaient inactif depuis des milliers d'année quand les *expéditions interdimensionnelles* d'il y a 50 ans, puis *l'incident de Christmas Island* ont provoqué leur réactivation, et la connexion des deux mondes. Depuis, les deux mondes se connaissent, et doivent coéxister, non sans quelques… réserves, parfois.
## Les quatres grandes civilisations
Les quatres grandes civilisations sont les quatres peuples qui ont les premiers été capable de voyager entre les mondes et de construire quelque chose hors de leur monde d'origine. Ce sont tous des peuples originaires du monde des animaux.
### La première grande civilisation
Peu est connu de la première grande civilisation. Ils sont surnommés les *Premiers* et autres surnoms vagues du genre, mais n'ont laissé que peu de trace, à part quelques éléments.
Ils sont théorisés comme étant les premiers utilisateurs de la puissance des *Chaos Emerald*, des *Power Rings*, et comme étant ceux qui ont créé de nombreux artefacts et entités tels que les *Super Warp Rings*. Nous ne savons cependant rien de leur culture, ni de leur évolution. Ils semblent avoir été présent sur les deux planètes, et avoir prospéré il y a plus de 10 000 ans.
### La seconde grande civilisation
***TODO***: écrire un truc dessus.
### La troisième grande civilisation
(note : voir *peuple-sidh*)
La troisième grande civilisation est le peuple Sidh ancien, qui a proposéré sur Sunlit Island.
### La quatrième grande civilisation
***TODO***: Choisir si je reprend les Nocturnus ou en fait une civilisation qui aurait exclavagiser les échidnés ?

View File

@ -0,0 +1,15 @@
# Subquest - ideas
- **The last of his kind** -- une histoire (peut-être en plusieur partie ?) où Shadow et Knuckles doivent travailler ensemble pour traquer le dernier Shadow Android. Knuckles ressent un peu de compassion envers le robot (qui a une conscience, et qui doit faire avec le fait de ne pas être Shadow et de savoir que le vrai Shadow à détruit une grande partie de son "espèce"), sachant ce que c'est d'être le dernier de son espace
- **Frozen Earth Theory** -- un event spécial noel, où Eggman à décidé de refroidir la planète comme chantage mondial, en précisant bien les riques pour les recoltes, l'alimentation, etc… Particularité graphique de ce scénario : Tout les niveaux utilisent les tiles de neige de Pearl Mountain.
- **Phantom Doll** -- Event special halloween : Tails Doll à récupéré un prototype de Phantom Ruby. Ouch.
- **The Chaos Breakers** -- La Team Dark est appellé sur Terre pour affronter de nouveaux ennemis, qui menace le portail dimensionnel : Les Chaos Breakers. Cette faction rebelle de GUN estime que les mobiens étant des aliens, on ne peut pas leur faire confiance, et qu'ils ont déjà apporté le chaos sur Terre.
- Un scénario Rouge + Amy
- Un scénario Cream + Omega
- Un scénario special saint-velentin Omega + Blaze :DDD
- Une série de scénario où GUN travaille pour connecter le monde de Blaze aux deux autres… malheureusement, des soucis se passeront.
- Scénario post-Forces avec l'explication de pourquoi GUN n'a pas aidé Mobius/le monde des animaux pendant l'invasion d'Eggman.

View File

@ -0,0 +1,79 @@
# Sonic Radiance - Battle System
Le système de combat de Sonic Radiance est un système de combat basée à la fois sur du tactical RPG et sur du JRPG classique. Les personnages et les ennemis sont placé sur une grille de 13×7 case, sur laquelle ils peuvent se déplacer de deux cases par tour.
Les personnages et ennemis agissent plusieurs fois par tour, entre 1 et 3 fois (hors augmentations comme le status *Speed Up*), et agissent à la suite du plus au moins rapide (avec un malus dans les "actions suivantes par tour", ce qui fait que les trois actions de Sonic ne seront pas forcément à la suite, mais cela pourra arriver tout de même). Les actions sont choisies juste avant le tours (à la Octopath Traveller)
## Compétences utilisables
- Attaquer : le personnage fait une série de trois attaques consécutives à 33% de l'attaque. Chaque attaques consécutives peut faire séparément un critique. Certaines capacités passives pourront augmenter le nombre d'attaques pour leur puissance.
- Compétences : Le personnage utilise l'une des compétences qui lui est propre. Voir dans les fiches de personnages les compétences utilisables.
- Objet : utilisation d'un objet (voir objets)
- Défendre : Le personnage se défend, lui permettant d'éviter ce tour-ci les effets d'un piège s'il se retrouve dessus, et d'encaisser une partie des dégats jusqu'à son tour suivant.
- Fuir : le personnage tente de fuir.
## Stats
Le système de statistique de Sonic Radiance est inspiré de Sonic Chronicles, mais amélioré pour offrir une vrai stat d'attaque et de défense aux personnages.
- **HP** :: les points utilisé pour la vie
- **PP** :: les points utilisé pour les POW Moves
- **Attack** :: Affecte les dégats commis avec des attaques directes
- **Defense** :: Affecte les dégats qu'on se prend par attaque physique
- **Technique** :: Affecte les coups critiques et les effets secondaires des attaques (face à Mind)
- **Power** :: Affecte les coups à distance (genre attaque de chaos)
- **Mind** :: Affecte la resistance aux attaques à distance (face à Power) et aux effets secondaires (face à Technique)
- **Speed** :: Affecte l'évasion et quand le personnage va attaquer
## Status
### Positif
- Camouflé : Augmente l'esquive
- Focus : Augmente chances de réussir un coup + coup critique
- Fortifié : Baisse les dégats subits
- Elem Fortif' : Comme Fortifié, mais rajoute des dégats de l'élément du bouclier
- Elem Power : Les attaques sont fortifié de 110% et rajoutent des dégats élémentaires
- Hyper : Augmente la puissance des coups (inspiré plus du mode Hyper de Sonic Fighter)
- Chanceux : Baisse les coups critique de l'ennemi + les risques de se prendre un effet secondaire
- Invincible : Annule tout les dégats subit
- Acceléré : Augmente vitesse + fait agir 1 fois supplémentaire
- Attrap'Anneaux : Augmente les anneaux gagné à la fin du combat
### Négatif
- Empoisonné : Perd 15% de PV/tour
- Affaibli : Baisse les dégats provoqués
- Vulnérable : Augmente les dégats subits
- Paralysé : Ne peut plus agir pendant x tours
- Endormi : Ne peut plus agir ni se déplacer. % de chance de se réveiller à chaque tours
- Maudit : L'ennemi subit plus facilement tout les effets secondaire et les coups critiques
- Distrait : L'ennemi rate plus souvent ses coups
- KO : ne peut plus agir, n'a plus de PV, etc.

View File

@ -0,0 +1,57 @@
# Sonic Boost
## Principe de base
Sonic Boost est un sub-game avec un aspect un peu "mobile" inspiré de plusieurs sources, qui fera partie du projet Sonic Radiance. Ce subgame sera utilise dans le scénario pour des phases "rapides", dans des quêtes annexes comme mini-jeu/mini-niveau, et apparaitra en tant que bonus complet (un peu à la manière du jeu de kart de Sonic Adventure 2)
Ses inspirations sont les suivantes :
- Le premier fangame jamais créé ([Sonic Boom](https://www.youtube.com/watch?v=jYT28un2pEY)), ainsi que son remake ([Neo Sonic Boom](https://www.youtube.com/watch?v=MSofjiB_4J8&t=577s)). Ce jeu se veut sur certain point une combinaison de ce premier fangame avec d'autres sources d'éléments.
- Shadow Shoot, un jeu mobile SEGA du Sonic Café, qui va être la principale source des graphismes et du gameplay de base. Le jeu va reprenddre ses graphismes, et le fait de déplacer sur 5 rails (mais l'aspect "shoot" sera réservé à certains personnages).
- La série de vieux fangame de course "Sonic R GM", "Sonic R DX", "Sonic R MV" de NICKtendo DS
- Les jeux mobile Sonic récent (c'est à dire post-Sonic Café) comme Sonic Dash ou Sonic Runners
- Smash Bros, sur certains points (aspect "collection" et tout)
## Modes de jeux (si c'est possible à mettre dans les temps)
Sonic Boost sera un subgame avec quelques utilisations dans le mode histoire, et quelques modes bonus accessible pour plus de rejouabilité. Les passages en mode "Sonic Boost" seront rejouable dans le menu en plus d'être jouable dans le jeu principal.
### Utilisation dans le mode de jeu principal
Le jeu principal et ses quêtes annexes contiendront plusieurs moments où vous devrez foncer à travers des niveaux en utilisant le gameplay type Boost. Si dans l'aventure principal ils ne seront pas forcément très souvent utilisé (3~4 fois au total), ils seront plus régulièrement présent en tant que niveau bonus permettant d'obtenir des emblèmes supplémentaires, ou en faisant partie de quêtes secondaires.
TODO: Definir exactement quels types de manière dont on peut jouer à un niveau.
Chaque niveau déjà joué sera rejouable dans le menu principal. Ce mode de jeu jeu s'inspirera alors à la fois du mode "Brawl/Smash" de Smash Bros, et de la manière dont se joue les niveaux de Sonic R GM. En effet, vous pourrez choisir votre personnage, puis choisir vos niveaux et la manière dont vous voulez y jouer (mission, mode "rival", etc).
### Mode "Endless"
Ce mode de jeu s'inspire des jeux comme Sonic Runners et Sonic Dash. Vous courrez dans l'un des niveaux disponible, avec des chunks qui se rajouteront au fur et à mesure. Vous avez un temps limité, mais qui se rempli à chaque checkpoint. Ces niveaux devront d'abord être débloqué dans chaque environnement de l'île pour être rejouable dans le menu principal.
Vous n'avez cependant qu'une vie, et le niveau se termine dès que vous perdez.
### Mode Classic
Inspiré du mode classic de Smash Bros, et du premier fangame (Sonic Boom / Neo Sonic Boom).
Vous jouez une série de 7 niveaux, chacun se terminant par un boss. Les niveaux sont choisi de manière semi aléatoire (pour chaque place, il y aura 2 niveaux possible). Dans chaque niveau, vous pouvez trouver une clef, qui vous permettra d'aller dans le special stage ensuite (sauf si vous perdez une vie). Comme dans Smash Ultimate, chaque personnage jouable aura son propre mini "scénario".
Si vous réussissez tout les special stage, vous pouvez aller au dernier niveau spécial "Milky Way".
Certains niveaux auront des gimmicks particulier, basé sur les différentes manières de jouer du mode normal (qui servira aussi à choisir des missions)
### Mode Tournoi
Il sera possible de débloquer quatre "coupe" dans le jeu principal en allant parler à Jet. Chaque coupe terminée pourra être rejouée dans le menu principal.
Cela consistera en un nombre de 4 coupes de 16 niveaux qui seront forcément en mode rival. Il sera possible d'utiliser des Wisps pour déconcentrer les adversaires, avec des effets similaires à ceux du mode combat.
### Mode "Shadow Shoot"
Ce mode de jeu sera aussi utilisé pour créer un clone de Shadow Shoot sur Mobile. Il sera possible de le débloqué après une quête annexe mineure centrée sur Shadow.
NOTE : Cela pourrait simplement être le mode Classic de Shadow ?

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

View File

@ -0,0 +1,41 @@
function love.conf(t)
t.identity = "space.kazhnuz.SonicRadiance" -- The name of the save directory (string)
t.version = "11.1" -- The LÖVE version this game was made for (string)
t.console = false -- Attach a console (boolean, Windows only)
t.accelerometerjoystick = false -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean)
t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean)
t.window.title = "Sonic Radiance" -- The window title (string)
t.window.icon = nil -- Filepath to an image to use as the window's icon (string)
t.window.width = 424 -- The window width (number)
t.window.height = 240 -- The window height (number)
t.window.borderless = false -- Remove all border visuals from the window (boolean)
t.window.resizable = false -- Let the window be user-resizable (boolean)
t.window.minwidth = 1 -- Minimum window width if the window is resizable (number)
t.window.minheight = 1 -- Minimum window height if the window is resizable (number)
t.window.fullscreen = false -- Enable fullscreen (boolean)
t.window.fullscreentype = "exclusive" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string)
t.window.vsync = true -- Enable vertical sync (boolean)
t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
t.window.display = 1 -- Index of the monitor to show the window in (number)
t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean)
t.window.x = nil -- The x-coordinate of the window's position in the specified display (number)
t.window.y = nil -- The y-coordinate of the window's position in the specified display (number)
t.modules.audio = true -- Enable the audio module (boolean)
t.modules.event = true -- Enable the event module (boolean)
t.modules.graphics = true -- Enable the graphics module (boolean)
t.modules.image = true -- Enable the image module (boolean)
t.modules.joystick = true -- Enable the joystick module (boolean)
t.modules.keyboard = true -- Enable the keyboard module (boolean)
t.modules.math = true -- Enable the math module (boolean)
t.modules.mouse = true -- Enable the mouse module (boolean)
t.modules.physics = true -- Enable the physics module (boolean)
t.modules.sound = true -- Enable the sound module (boolean)
t.modules.system = true -- Enable the system module (boolean)
t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
t.modules.touch = true -- Enable the touch module (boolean)
t.modules.video = true -- Enable the video module (boolean)
t.modules.window = true -- Enable the window module (boolean)
t.modules.thread = true -- Enable the thread module (boolean)
end

View File

@ -0,0 +1,38 @@
-- core/debug.lua :: Debug functions for the core system.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local DebugSystem = Object:extend()
local lovebird = require("libs.lovebird")
function DebugSystem:new(controller, active)
self.controller = controller
lovebird.update()
self.active = active or false
end
function DebugSystem:update(dt)
lovebird.update(dt)
end
return DebugSystem

View File

@ -0,0 +1,71 @@
-- core/init.lua :: The main file of the core system, an object full of subsystem
-- loaded by the game to handle the main functions (like screen, translation,
-- inputs…)
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local CoreSystem = Object:extend()
local DebugSystem = require "core.debug"
local Options = require "core.options"
local Input = require "core.input"
local Screen = require "core.screen"
local Lang = require "core.lang"
local SceneManager= require "core.scenemanager"
function CoreSystem:new()
self.debug = DebugSystem(self)
self.options = Options(self)
self.input = Input(self)
self.screen = Screen(self)
self.scenemanager = SceneManager(self)
end
function CoreSystem:mousemoved(x, y, dx, dy)
local x, y = self.screen:project(x, y)
local dx, dy = self.screen:project(dx, dy)
self.scenemanager:mousemoved(x, y, dx, dy)
end
function CoreSystem:mousepressed( x, y, button, istouch )
local x, y = self.screen:project(x, y)
self.scenemanager:mousepressed( x, y, button, istouch )
end
function CoreSystem:update(dt)
self.debug:update(dt)
self.input:update(dt)
self.scenemanager:update(dt)
end
function CoreSystem:draw()
self.scenemanager:draw()
end
function CoreSystem:exit()
self.options:save()
love.event.quit()
end
return CoreSystem

View File

@ -0,0 +1,108 @@
-- core/input.lua :: The input system. This object take care of transforming the
-- differents inputs source in a virtual controller for the game.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local InputManager = Object:extend()
function InputManager:new(controller)
self.controller = controller
self.data = self.controller.options.data.input[1]
self.keys = {}
for k,v in pairs(self.data.keys) do
self.keys[k] = {}
self.keys[k].isDown = false
self.keys[k].isPressed = false
self.keys[k].isReleased = false
self.keys[k].test = "ok"
end
self.fakekeys = {}
for k,v in pairs(self.data.keys) do
self.fakekeys[k] = {}
self.fakekeys[k].isDown = false
self.fakekeys[k].isPressed = false
self.fakekeys[k].isReleased = false
self.fakekeys[k].test = "ok"
end
end
function InputManager:isDown(padkey)
local isdown = false
if self.data.type == "keyboard" then
local key = self.data.keys[padkey]
isdown = love.keyboard.isDown(key)
if isdown then
end
else
print("Warning: unsupported input device")
end
return isdown
end
function InputManager:translateAction(key)
--TODO:depreciated function
local padkey = ""
for k,v in pairs(self.data.keys) do
if v == key then padkey = k end
end
return padkey
end
function InputManager:getKey(padkey)
local padkey = padkey
for k,v in pairs(self.data.keys) do
if (k == padkey) then key = v end
end
return key
end
function InputManager:update(dt)
for k,v in pairs(self.keys) do
local isDown = self:isDown(k)
if (isDown) then
if not (self.keys[k].isDown) then
self.keys[k].isDown = true
self.keys[k].isPressed = true
self.keys[k].isReleased = false
else
if (self.keys[k].isPressed) then
self.keys[k].isPressed = false
end
end
else
if (self.keys[k].isDown) then
self.keys[k].isDown = false
self.keys[k].isPressed = false
self.keys[k].isReleased = true
else
if (self.keys[k].isReleased) then
self.keys[k].isReleased = false
end
end
end
end
end
return InputManager

View File

@ -0,0 +1,52 @@
-- core/langs.lua :: The translation system. Transform a string to another
-- according to the translations files in the datas/ folder.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local LanguageManager = Object:extend()
local langs = require "datas.languages"
function LanguageManager:new(controller)
self.controller = controller
self:setLang(self.controller.options.data.language)
end
function LanguageManager:getStringList(library, file)
return require(self.lang .. "." .. library .. "." .. file)
end
function LanguageManager:getLangName(lang)
local langnames = langs.available_langs
return langnames[lang]
end
function LanguageManager:getCurrentLangName()
local langnames = langs.available_langs
return langnames[self.lang]
end
function LanguageManager:setLang(lang)
self.controller.options.data.language = lang
self.lang = self.controller.options.data.language
end
return LanguageManager

View File

@ -0,0 +1,87 @@
-- assets/animator :: the animator object. The animator object handle what
-- frame a sprite should draw.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Animator = Object:extend()
function Animator:new(sprite)
self.sprite = sprite
self.frame = 1
self.frameTimer = 0
self.currentAnimation = ""
self.animationData = {}
self.customSpeed = 0
self:changeToDefaultAnimation()
end
function Animator:setCustomSpeed(customSpeed)
self.customSpeed = customSpeed or 0
end
function Animator:update(dt)
if (self.currentAnimation == "") then
print("warning: no current animation data")
return 0
end
local speed = self.animationData.speed
if (self.animationData.speed) == -1 then
speed = self.customSpeed --math.abs(self.xsp / 16)
end
self.frameTimer = self.frameTimer + (speed * dt)
if self.frameTimer > 1 then
self.frameTimer = 0
if self.frame == self.animationData.endAt then
self.frame = self.animationData.loop
else
self.frame = self.frame + 1
end
end
end
function Animator:getFrame()
return self.frame
end
function Animator:draw(x, y, r, sx, sy, ox, oy, kx, ky)
self.sprite:drawFrame(self.frame, x, y, r, sx, sy, ox, oy, kx, ky)
end
function Animator:changeAnimation(name, restart)
self.currentAnimation = name
self.animationData = self.sprite.data.animations[self.currentAnimation]
local restart = restart or true
if (restart) then
self.frame = self.animationData.startAt
self.frameTimer = 0
end
end
function Animator:changeToDefaultAnimation(restart)
self:changeAnimation(self.sprite.data.metadata.defaultAnim, restart)
end
return Animator

View File

@ -0,0 +1,110 @@
-- assets/autotile :: The autotile object : this is an object that draw tiles
-- automatically with borders in rectangles.
-- It works with a 3×3 tileset showing all borders.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Tileset = require "core.modules.assets.tileset"
local Autotile = Object:extend()
function Autotile:new(filepath)
self.tileset = Tileset(filepath)
self.data = require(filepath .. ".lua")
self.metadata = self.data.metadata
self.tilesize = self.metadata.width
end
function Autotile:drawtile(i, j, x, y, r, sx, sy, ox, oy, kx, ky)
local i = i or 1
local j = j or 1
local tilesize = self.tilesize / 2
i = (i - 1) * 2 + 1
j = (j - 1) * 2 + 1
self.tileset:drawTile_Grid(i , j , x , y , r, sx, sy, ox, oy, kx, ky)
self.tileset:drawTile_Grid(i + 1, j , x + tilesize, y , r, sx, sy, ox, oy, kx, ky)
self.tileset:drawTile_Grid(i , j + 1, x , y + tilesize, r, sx, sy, ox, oy, kx, ky)
self.tileset:drawTile_Grid(i + 1, j + 1, x + tilesize, y + tilesize, r, sx, sy, ox, oy, kx, ky)
end
function Autotile:draw(x, y, w, h)
local w = w or self.tilesize
local h = h or self.tilesize
w = math.max(math.floor(w / self.tilesize), 1)
h = math.max(math.floor(h / self.tilesize), 1)
local halfsize = self.tilesize / 2
local tilesize = self.tilesize
if (w == 1) then
self.tileset:drawtile_Grid(1, 1, x , y)
self.tileset:drawtile_Grid(1, 6, x , y + (h*2 - 1) * halfsize)
self.tileset:drawtile_Grid(6, 1, x + (w*2 - 1) * halfsize, y)
self.tileset:drawtile_Grid(6, 6, x + (w*2 - 1) * halfsize, y + (h*2 - 1) * halfsize)
if (h > 1) then
h = h - 1
for i = 1, h do
self.tileset:drawtile_Grid(1, 3, x, y + (i * tilesize) - halfsize)
self.tileset:drawtile_Grid(6, 3, x + halfsize, y + (i * tilesize) - halfsize)
self.tileset:drawtile_Grid(1, 4, x , y + (i * tilesize))
self.tileset:drawtile_Grid(6, 4, x + halfsize, y + (i * tilesize))
end
end
-- draw just one stuff
else
if (h == 1) then
self.tileset:drawtile_Grid(1, 1, x , y)
self.tileset:drawtile_Grid(1, 6, x , y + (h*2 - 1) * halfsize)
self.tileset:drawtile_Grid(6, 1, x + (w*2 - 1) * halfsize, y)
self.tileset:drawtile_Grid(6, 6, x + (w*2 - 1) * halfsize, y + (h*2 - 1) * halfsize)
w = w - 1
for i = 1, w do
self.tileset:drawtile_Grid(3, 1, x + (i * tilesize) - halfsize, y)
self.tileset:drawtile_Grid(3, 6, x + (i * tilesize) - halfsize, y + halfsize)
self.tileset:drawtile_Grid(4, 1, x + (i * tilesize) , y )
self.tileset:drawtile_Grid(4, 6, x + (i * tilesize), y +halfsize)
end
else
self:drawtile(1, 1, x , y)
self:drawtile(1, 3, x , y + (h - 1) * tilesize)
self:drawtile(3, 1, x + (w - 1) * tilesize, y)
self:drawtile(3, 3, x + (w - 1) * tilesize, y + (h - 1) * tilesize)
w = w - 2
h = h - 2
for i=1, w do
self:drawtile(2, 1, i * tilesize, y)
self:drawtile(2, 3, i * tilesize, y + (h + 1) * tilesize)
for j=1, h do
self:drawtile(2, 2, i * tilesize, j * tilesize)
end
end
for i=1, h do
self:drawtile(1, 2, x , i * tilesize)
self:drawtile(3, 2, x + (w + 1) * tilesize, i * tilesize)
end
end
end
end
return Autotile

View File

@ -0,0 +1,50 @@
-- assets/sprite :: the background object, which is an image that draw itself
-- automatically to fill a texture.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Background = Object:extend()
function Background:new(filepath)
self.image = love.graphics.newImage(filepath)
self.batch = love.graphics.newSpriteBatch(self.image , 1000 )
self.width, self.height = self.image:getDimensions()
local w = math.floor(424 / self.width) * self.width + 1
local h = math.floor(240 / self.height) * self.height + 1
for i=-1, w do
for j=-1, h do
self.batch:add(i * self.width, j * self.height)
j = j + 1
end
i = i + 1
end
end
function Background:draw(ox, oy)
love.graphics.setColor(1, 1, 1)
love.graphics.draw(self.batch, ox, oy)
end
return Background

View File

@ -0,0 +1,178 @@
-- assets/fonts :: the fonts object, which is a simple way to draw text with font.
-- Some of these functions are quite slow, so it's better to use them to generate
-- texture instead of text.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Font = Object:extend()
-- Initilizing and configuring option
function Font:new(filename, size)
local filename = filename
self.font = love.graphics.newFont(filename, size)
self.filter = ""
self:setColor(1, 1, 1, 1)
self:setSpacing(false, 0)
self.align = "left"
end
function Font:set()
love.graphics.setFont(self.font)
end
function Font:setColor(r, g, b, a)
self.color = {}
self.color.r = r
self.color.g = g
self.color.b = b
self.color.a = a
end
function Font:setColorFromTable(color)
self.color = color
end
function Font:setSpacing(use_custom, size)
self.spacing = {}
self.spacing.active = use_custom
self.spacing.size = size
end
function Font:setAlign(align)
self.align = align
end
function Font:setFilter(filter)
self.filter = filter
end
function Font:setLineHeight(height)
self.font:setLineHeight(height)
end
-- get information functions
function Font:getHeight()
local font = self.font
return font:getHeight()
end
function Font:getWidth(string)
local spacing = 0
if (self.spacing.active == true) then
local charNumber = string.len(string)
spacing = self.spacing.size * charNumber
end
local width = self.font:getWidth(string) + spacing
return width
end
function Font:getColor()
return self.color
end
-- print functions
function Font:draw(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
-- draw text with color and effect applied
local limit = limit or 0
local align = align or self.align
self:applyFilter(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
love.graphics.setColor(self.color.r, self.color.g, self.color.b, self.color.a)
self:printf(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
end
function Font:print(text, x, y, align, r, sx, sy, ox, oy, kx, ky)
self:set()
if (self.spacing.active) then
utils.draw.printWithSpacing(text, self.spacing.size, align, x, y, r, sx, sy, ox, oy, kx, ky)
else
utils.draw.print(text, align, x, y, r, sx, sy, ox, oy, kx, ky)
end
end
function Font:printf(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
self:set()
if (limit > 0) then
love.graphics.printf(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
else
self:print(text, x, y, align, r, sx, sy, ox, oy, kx, ky)
end
end
-- FILTER SYSTEM
function Font:applyFilter(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
if self.filter == "shadow" then
self:applyFilterShadow(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
elseif self.filter == "border" then
self:applyFilterBorder(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
elseif self.filter == "doubleborder" then
self:applyFilterDoubleBorder(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
end
end
function Font:applyFilterShadow(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
love.graphics.setColor(0, 0, 0, 1)
self:printf(text, x+1, y+1, limit, align, align, r, sx, sy, ox, oy, kx, ky)
utils.draw.resetColor()
end
function Font:applyFilterBorder(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
love.graphics.setColor(0, 0, 0, 1)
self:printf(text, x-1, y-1, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x , y-1, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x+1, y-1, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x+1, y , limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x-1, y , limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x-1, y+1, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x , y+1, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x+1, y+1, limit, align, r, sx, sy, ox, oy, kx, ky)
utils.draw.resetColor()
end
function Font:applyFilterDoubleBorder(text, x, y, limit, align, r, sx, sy, ox, oy, kx, ky)
love.graphics.setColor(0, 0, 0, 1)
self:printf(text, x-2, y-2, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x , y-2, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x+2, y-2, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x+2, y , limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x-2, y , limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x-2, y+2, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x , y+2, limit, align, r, sx, sy, ox, oy, kx, ky)
self:printf(text, x+2, y+2, limit, align, r, sx, sy, ox, oy, kx, ky)
utils.draw.resetColor()
end
return Font

View File

@ -0,0 +1,187 @@
-- modules/assets :: a simple assets manager, aim to put every assets in a simple
-- serie of table in order to find them easily.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Assets = Object:extend()
local Sprite = require "core.modules.assets.sprites"
local Font = require "core.modules.assets.fonts"
local Tileset = require "core.modules.assets.tileset"
local Autotile = require "core.modules.assets.autotile"
local Background = require "core.modules.assets.background"
function Assets:new()
self.sprites = {}
self.sfx = {}
self.fonts = {}
self.music = nil
self:clearBackgrounds()
self:clearFonts()
self:clearAutotile()
self:clearTileset()
self.images = {}
end
function Assets:init()
self.sprites = {}
self.sfx = {}
self.fonts = {}
self.music = nil
self.backgrounds= {}
self:clearFonts()
self.images = {}
end
function Assets:clear()
-- TODO: destroy individually each texture/image when assets are cleared
self.sprites = {}
self.sfx = {}
self.fonts = {}
self.music = nil
self.backgrounds= {}
self:clearFonts()
self.images = {}
end
function Assets:update(dt)
self:animationsUpdate(dt)
end
-- SFX et Musique
function Assets:addSFX(name, filepath)
self:newSFX(name, filepath)
end
function Assets:newSFX(name, filepath)
self.sfx[name] = love.audio.newSource( filepath, "static" )
end
function Assets:clearSFX()
love.audio.stop( )
self.sfx = {}
end
function Assets:setMusic(filename)
if filename ~= nil then
love.audio.stop( )
self.music = love.audio.newSource(filename, "stream" )
self.music:setVolume(game.options.data.audio.music / 100)
end
end
function Assets:playSFX(filename)
if not (self.sfx[filename] == nil) then
self.sfx[filename]:stop()
self.sfx[filename]:setVolume(game.options.data.audio.sfx / 100)
love.audio.play( self.sfx[filename] )
end
end
function Assets:playMusic()
if not (self.music == nil) then
love.audio.play(self.music)
end
end
function Assets:silence()
love.audio.stop()
end
-- Background --
function Assets:addImage(name, filename)
self.images[name] = love.graphics.newImage(filename)
end
function Assets:drawImage(name, x, y, r, sx, sy, ox, oy, kx, ky)
love.graphics.draw(self.images[name], x, y, r, sx, sy, ox, oy, kx, ky)
end
-- Images --
function Assets:clearBackgrounds()
self.backgrounds = {}
end
function Assets:addBackground(name, filepath)
self.backgrounds[name] = Background(filepath)
end
-- SPRITES --
function Assets:addSprite(name, filepath)
self.sprites[name] = Sprite(filepath)
end
function Assets:clearSprites()
self.sprites = {}
end
function Assets:animationsUpdate(dt)
for i,v in pairs(self.sprites) do
v:update(dt)
end
end
-- FONTS --
function Assets:clearFonts()
self.fonts = {}
end
function Assets:addFont(key, filename, size)
local font = Font(filename, size)
self.fonts[key] = font
end
function Assets:getFont(filename)
return self.fonts[filename]
end
-- Tileset
function Assets:addTileset(name, filepath)
self.tileset[name] = Tileset(filepath)
end
function Assets:clearTileset()
self.tileset = {}
end
-- Autotile
function Assets:addAutotile(name, tilesize)
self.autotile[name] = Autotile(name, tilesize)
end
function Assets:clearAutotile()
self.autotile = {}
end
return Assets

View File

@ -0,0 +1,78 @@
-- assets/sprite :: the assets object, which is basically a tileset animated by
-- an animator object. An animator object is always tied to a sprite, but a sprite
-- can use different animator in order to make several animator share the same sprite
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Sprite = Object:extend()
local Animator = require("core.modules.assets.animator")
local Tileset = require("core.modules.assets.tileset")
function Sprite:new(filepath)
self.tileset = Tileset(filepath)
self.data = require(filepath)
self.animator = Animator(self)
self.customSpeed = 0
self:changeToDefaultAnimation(true)
end
function Sprite:update(dt)
self.animator:update(dt)
end
function Sprite:setCustomSpeed(customSpeed)
self.animator:setCustomSpeed(customSpeed)
end
function Sprite:changeToDefaultAnimation(restart)
self.animator:changeToDefaultAnimation(restart)
end
function Sprite:changeAnimation(name, restart)
self.animator:changeAnimation(name, restart)
end
function Sprite:drawAnimation(x, y, r, sx, sy, ox, oy, kx, ky)
self.animator:draw(x, y, r, sx, sy, ox, oy, kx, ky)
end
function Sprite:drawFrame(frame, x, y, r, sx, sy, ox, oy, kx, ky)
self.tileset:drawTile(frame, x, y, r, sx, sy, ox, oy, kx, ky)
end
function Sprite:drawPart(x, y, w, h, r, sx, sy, ox, oy, kx, ky)
local w = math.floor(w)
local h = math.floor(h)
if w >= 0 and h <= 0 then
return 0
end
love.graphics.setScissor(x - ox, y - oy, w, h)
self:drawAnimation(x, y, r, sx, sy, ox, oy, kx, ky)
love.graphics.setScissor( )
end
return Sprite

View File

@ -0,0 +1,89 @@
-- assets/tileset :: tileset are automatic breakage of texture into quads. they
-- have the adventage of being automatized, reducing the ammount of code needed
-- to create quads.
-- They have two manners to be draw: with their quad id (in 1D) or by using their
-- place in the grid, in 2D.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Tileset = Object:extend()
function Tileset:new(filepath)
self.texture = love.graphics.newImage(filepath .. ".png")
local data = require(filepath)
self.metadata = data.metadata
self:createQuads()
end
function Tileset:createGrid()
self.textureWidth, self.textureHeight = self.texture:getDimensions()
self.width, self.height = self.metadata.width, self.metadata.height
self.gridWidth, self.gridHeight = math.floor(self.textureWidth / self.width),
math.floor(self.textureHeight / self.height)
end
function Tileset:createQuads()
self.quads = {}
self:createGrid()
local quad, n
n = 1
for i=0, (self.gridHeight-1) do
for j=0, (self.gridWidth-1) do
quad = love.graphics.newQuad(j * self.width, i * self.height, self.width, self.height, self.textureWidth, self.textureHeight)
self.quads[n] = quad
n = n + 1
end
end
end
function Tileset:getTileID_Grid(x, y)
local n = (y - 1) * self.gridWidth + x
return n
end
function Tileset:getTile_Grid(x, y)
return self:getTile(self:getTileID_Grid(x, y))
end
function Tileset:getTile(n)
return self.quads[n]
end
function Tileset:drawTile_Grid(i, j, x, y, r, sx, sy, ox, oy, kx, ky)
local tileID = self:getTileID_Grid(i, j)
love.graphics.draw(self.texture, self.quads[tileID], x, y, r, sx, sy, ox, oy, kx, ky)
end
function Tileset:drawTile(id, x, y, r, sx, sy, ox, oy, kx, ky)
love.graphics.draw(self.texture, self.quads[id], x, y, r, sx, sy, ox, oy, kx, ky)
end
return Tileset

View File

@ -0,0 +1,59 @@
-- scenes.lua :: the scene object, that aim to give a better control to the engine
-- to the different scene, without having to call too much boilerplate
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Scene = Object:extend()
local Assets = require "core.modules.assets"
function Scene:new()
self.mouse = {}
self.mouse.x, self.mouse.y = core.screen:getMousePosition()
self.assets = Assets()
end
function Scene:register()
core.scenemanager.currentScene = self
end
function Scene:update(dt)
-- Empty function, is just here to avoid crash
end
function Scene:mousemoved(x, y, dx, dy)
-- Empty function, is just here to avoid crash
end
function Scene:mousepressed( x, y, button, istouch )
-- Empty function, is just here to avoid crash
end
function Scene:draw()
end
function Scene:clear()
end
return Scene

View File

@ -0,0 +1,96 @@
-- core/options.lua :: The options loading/saving system. Is used by the other
-- modules to save their settings.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local OptionsManager = Object:extend()
local binser = require "libs.binser"
function OptionsManager:new()
-- We begin by creating an empty data table before reading the data.
self.data = {}
self:read()
end
function OptionsManager:reset()
-- Reset the option to the game defaults.
self.data.video = {}
self.data.video.crtfilter = false
self.data.video.resolution = 1
self.data.video.border = true
self.data.video.vsync = false
self.data.video.fullscreen = false
-- We load the default files
self.data.input = require "datas.inputs"
-- TODO: have a way to auto-load a language according to the OS ?
self.data.language = "en"
self.data.audio = {}
self.data.audio.music = 100
self.data.audio.sfx = 100
end
function OptionsManager:getFile(absolute)
local dir = ""
if absolute then
dir = love.filesystem.getSaveDirectory() .. "/"
if not utils.filesystem.exists(dir) then
love.filesystem.createDirectory( "" )
end
end
local filepath = dir .. "options.data"
return filepath
end
function OptionsManager:write()
local data = self:getData()
filepath = self:getFile(true)
binser.writeFile(filepath, data)
end
function OptionsManager:read()
filepath = self:getFile(true)
if utils.filesystem.exists("options.data") then
local loadedDatas = binser.readFile(filepath)
print("data file found, loading it")
self:setData(loadedDatas[1])
else
self:reset()
print("no data file found, reseting data")
end
end
function OptionsManager:getData(data)
return self.data
end
function OptionsManager:setData(data)
self.data = data
end
return OptionsManager

View File

@ -0,0 +1,64 @@
-- scene.lua :: a basic scene management system, that work by sending the different
-- core functions to the scene, normally without the scene itself having to manage
-- them.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local SceneManager = Object:extend()
function SceneManager:new(controller)
self.controller = controller
self.currentScene = nil
end
function SceneManager:update(dt)
if (self.currentScene ~= nil) then
local keys = self.controller.input.keys
self.currentScene.keys = keys
self.currentScene.assets:update(dt)
self.currentScene:update(dt)
end
end
function SceneManager:mousemoved(x, y, dx, dy)
self.currentScene.mouse.x,
self.currentScene.mouse.y = x, y
self.currentScene:mousemoved(x, y, dx, dy)
end
function SceneManager:mousepressed( x, y, button, istouch )
self.currentScene:mousepressed( x, y, button, istouch )
end
function SceneManager:clearScene()
self.currentScene = nil
end
function SceneManager:draw()
self.controller.screen:apply()
if (self.currentScene ~= nil) then
self.currentScene:draw(dt)
end
self.controller.screen:cease()
end
return SceneManager

View File

@ -0,0 +1,72 @@
-- core/screen.lua :: Basic screen manager. Use CScreen as a backend, and works
-- as an abstraction layer around CScreen.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local ScreenManager = Object:extend()
local CScreen = require "libs.cscreen"
local SCREEN_HEIGHT = 240
local SCREEN_WIDTH = 424
function ScreenManager:new(controller)
self.controller = controller
self.data = self.controller.options.data.video
self:applySettings()
CScreen.init(SCREEN_WIDTH, SCREEN_HEIGHT, true)
CScreen.setColor(0, 0, 0, 1)
love.graphics.setDefaultFilter( "nearest", "nearest", 1 )
end
function ScreenManager:applySettings()
self.data = self.controller.options.data.video
local flags = {}
flags.vsync = self.data.vsync
flags.borderless = (self.data.border == false)
love.window.setMode(SCREEN_WIDTH * self.data.resolution, SCREEN_HEIGHT * self.data.resolution, flags)
love.window.setFullscreen( self.data.fullscreen )
local width, height = love.window.getMode()
CScreen.update(width, height)
end
function ScreenManager:project(x, y)
return CScreen.project(x, y)
end
function ScreenManager:getMousePosition()
return CScreen.project(love.mouse.getX(), love.mouse.getY())
end
function ScreenManager:apply()
CScreen.apply()
end
function ScreenManager:cease()
CScreen.cease()
end
return ScreenManager

View File

@ -0,0 +1 @@
return {"sonic"}

View File

@ -0,0 +1,57 @@
return {
name = "Default",
name_full = "Default the Character",
class = "Speedster",
startlevel = 1,
isUnlockedAtStart = true,
base_stats = {
hpmax = 200, --
ppmax = 50, --
attack = 50, --
power = 50, --
defense = 50, --
technic = 50, --
mind = 50, --
speed = 50, --
turns = 3, -- number of attacks by turn (unused)
move = 2, -- how far the character can get in one turn
},
color = {1, 1, 1},
skill_list = {
--{attack_name, level},
},
flags = {
canGoSuper = true,
},
assets = {
charset = {"", 1},
lifeicon = 1,
spriteset = "sonic",
},
inventory = {
haveShoes = true,
haveMechs = false,
haveGlove = true,
haveHammer= false,
accessories_number = 3,
chao_number = 1,
},
boost_stats = {
spd = 5,
jmp = 3,
jumpaction = "doublejump",
jumpaction_power = 2,
action = "spinattack",
action_power = 1,
canBreakCraft = false,
}
}

View File

@ -0,0 +1,9 @@
return {
"sonic",
"tails",
"knuckles",
"amy",
"cream",
"shadow",
"rouge",
}

View File

@ -0,0 +1,70 @@
return {
name = "Default",
name_full = "Default the Character",
class = "Speedster",
startlevel = 100,
isUnlockedAtStart = true,
base_stats = {
hpmax = 200, --
ppmax = 50, --
attack = 50, --
power = 50, --
defense = 50, --
technic = 50, --
mind = 50, --
speed = 50, --
turns = 3, -- number of attacks by turn (unused)
move = 3, -- how far the character can get in one turn
},
color = {1, 1, 1},
skill_list = {
--{attack_name, level},
{"spinattack", 2},
{"spinjump", 3},
{"spindash", 8},
{"hommingattack", 11},
{"spinattack", 15},
{"sonicflare", 18},
{"bluetornado", 22},
{"spindash", 26},
{"soniccracker", 30},
{"hommingattack", 35},
{"bluetornado", 40},
{"boost", 62},
{"lightspeedattack", 70},
},
flags = {
canGoSuper = true,
},
assets = {
charset = {"", 1},
lifeicon = 1,
spriteset = "sonic",
},
inventory = {
haveShoes = true,
haveMechs = false,
haveGlove = true,
haveHammer= false,
accessories_number = 3,
chao_number = 1,
},
boost_stats = {
spd = 5,
jmp = 3,
jumpaction = "doublejump",
jumpaction_power = 2,
action = "spinattack",
action_power = 1,
canBreakCraft = false,
}
}

View File

@ -0,0 +1,16 @@
return {
[1] = {
type = "keyboard",
keys = {
["left"] = "left",
["right"] = "right",
["up"] = "up",
["down"] = "down",
["A"] = "a",
["B"] = "z",
["C"] = "e",
["start"] = "return",
["select"] = "space"
}
}
}

View File

@ -0,0 +1,4 @@
return {
['false'] = "False",
['true'] = "True"
}

View File

@ -0,0 +1,18 @@
return {
["options"] = "OPTIONS",
["video"] = "VIDEO",
["audio"] = "AUDIO",
["lang"] = "LANGUES",
["input"] = "CONTROLES",
["reset"] = "RESET",
["exit"] = "EXIT",
["back"] = "BACK",
["sfx"] = "SFX",
["music"] = "MUSIC",
["keyboard"] = "Clavier",
["inputtype"] = "SOURCE",
["vsync"] = "VSYNC",
["borders"] = "BORDURES",
["fullscreen"] = "PLEIN ECRAN",
["resolution"] = "RESOLUTION"
}

View File

@ -0,0 +1,4 @@
return {
['false'] = "Faux",
['true'] = "Vrai"
}

View File

@ -0,0 +1,18 @@
return {
["options"] = "SETTINGS",
["video"] = "VIDEO",
["audio"] = "AUDIO",
["lang"] = "LANGUAGES",
["input"] = "CONTROLS",
["reset"] = "RESET",
["exit"] = "EXIT",
["back"] = "BACK",
["sfx"] = "SFX",
["music"] = "MUSIC",
["keyboard"] = "Keyboard",
["inputtype"] = "SOURCE",
["vsync"] = "VSYNC",
["borders"] = "BORDERS",
["fullscreen"] = "FULLSCREEN",
["resolution"] = "RESOLUTION"
}

View File

@ -0,0 +1,6 @@
return {
available_langs = {
["en"] = "English",
["fr"] = "Français"
}
}

View File

@ -0,0 +1,199 @@
-- game/characters :: The character handler. This object handle all the character
-- and is able to get and set datas about them.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local CharacterManager = Object:extend()
function CharacterManager:new(controller)
self.controller = controller
self.namelist = require "datas.characters"
self.list = {}
self.team = require "datas.characters.baseteam"
self.active = 1
self:init()
end
function CharacterManager:init()
for k, v in pairs(self.namelist) do
local dir = "datas/characters/" .. v .. ".lua"
local fileinfo = love.filesystem.getInfo(dir)
if fileinfo ~= nil then
self:initCharacter(v)
end
end
end
function CharacterManager:getCharacterData(charname)
-- va eprmettre de récupérer les données d'un personnage
return require("datas.characters." .. charname)
end
function CharacterManager:initCharacter(id)
local stats = {}
local character = self:getCharacterData(id)
stats.level = character.startlevel
stats.exp = self:getExpValue(stats.level)
stats.exp_next = self:getExpValue(stats.level + 1)
stats.hpmax = character.base_stats.hpmax
stats.ppmax = character.base_stats.ppmax
stats.attack = character.base_stats.attack
stats.power = character.base_stats.power
stats.defense = character.base_stats.defense
stats.technic = character.base_stats.technic
stats.mind = character.base_stats.mind
stats.speed = character.base_stats.speed
stats.turns = character.base_stats.turns
character.stats = stats
self.list[id] = character
self:recalculateStats(id)
stats.hp = stats.hpmax
stats.pp = stats.ppmax
stats.status = 0
character.stats = stats
self.list[id] = character
end
function CharacterManager:getExpValue(level)
return math.floor( ( 4 * ( level ^ 3 ) ) / 5 )
end
function CharacterManager:setLevel(id, newlevel)
self.list[id].stats.level = newlevel
local stats = self.list[id].stats
local exp, exp_next, exp_current
exp = self:getExpValue(stats.level)
exp_next = self:getExpValue(stats.level + 1)
exp_current = self.list[id].stats.exp
self.list[id].stats.exp = math.max(math.min(exp_current, exp_next - 1), exp)
self.list[id].stats.exp_next = exp_next
self:recalculateStats(id)
end
function CharacterManager:levelUp(id)
self:setLevel(id, self.list[id].stats.level + 1)
end
function CharacterManager:getStatValue(level, base)
return math.floor( (((base * 2) * level)/100) ) + 5
end
function CharacterManager:getHPValue(level, base)
return math.floor( (((base * 2.7) * level)/100) ) + 15 + level
end
function CharacterManager:getPPValue(level, base)
return math.floor( (((base * 1.5) * level)/100) ) + 8
end
function CharacterManager:recalculateStats(id)
local character = self.list[id]
local stats = character.stats
local base_stats = character.base_stats
stats.hpmax = self:getHPValue(stats.level, base_stats.hpmax)
stats.ppmax = self:getPPValue(stats.level, base_stats.ppmax)
stats.attack = self:getStatValue(stats.level, base_stats.attack)
stats.power = self:getStatValue(stats.level, base_stats.power)
stats.defense = self:getStatValue(stats.level, base_stats.defense)
stats.mind = self:getStatValue(stats.level, base_stats.mind)
stats.technic = self:getStatValue(stats.level, base_stats.technic)
stats.speed = self:getStatValue(stats.level, base_stats.speed)
end
function CharacterManager:getSkillList(id)
local character = self.list[id]
local learnedlist = {}
for i, v in ipairs(character.skill_list) do
local tech_name, tech_level, isLearned = v[1], v[2], false
if tech_level <= character.stats.level then
if learnedlist[tech_name] == nil then
learnedlist[tech_name] = 1
else
learnedlist[tech_name] = learnedlist[tech_name] + 1
end
end
-- On continue ensuite d'itérer dans la liste
end
return learnedlist
end
function CharacterManager:getData()
local data = {}
data.list = self.list
data.team = self.team
return data
end
function CharacterManager:setData(data)
local data = data
self.list = data.list
self.team = data.team
end
function CharacterManager:heal(id)
self.list[id].stats.hp = self.list[id].stats.hpmax
self.list[id].stats.hp = self.list[id].stats.ppmax
self.list[id].stats.status = 0
end
function CharacterManager:addToTeam(id)
self:heal(id)
table.insert(self.team, id)
end
function CharacterManager:removeToTeam(teamid)
self.team[teamid] = ""
end
function CharacterManager:getActiveCharacter()
return self.team[self.active]
end
-- DEBUG FUNCTIONS
function CharacterManager:printCharacter(id)
local character = self.list[id]
local stats = character.stats
print(id .. ". " .. character.fullname)
print("Lvl " .. character.stats.level .. " (" .. stats.exp .. "/" .. stats.exp_next .. " exp)")
end
function CharacterManager:printTeam()
for i,v in ipairs(self.team) do
self:printCharacter(v)
print("-----")
end
end
return CharacterManager

View File

@ -0,0 +1,140 @@
-- game :: The main game subsystem. Basically a big object that handle all the
-- game-related data like characters, monsters, etc. While the core aim to be
-- reusable at will, the game is specifically made for the current game.
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Game = Object:extend()
local binser = require "libs.binser"
function Game:new()
self.slot = -1
self.gametime = 0
end
function Game:setData(data)
local data = data
self.gametime = data.gametime
end
function Game:getData()
local data = {}
data.gametime = self.gametime
return data
end
function Game:read(save_id)
self.slot = save_id
if (self.slot > 0) then
filepath = self:getSaveFile(self.slot, true)
if love.filesystem.exists("save" .. self.slot .. ".save") then
local loadedDatas = binser.readFile(filepath)
self:setData(loadedDatas[1])
end
end
end
function Game:write(save_id)
if (self.slot > 0) then
local data = self:getData()
filepath = self:getSaveFile(self.slot, true)
binser.writeFile(filepath, data)
end
end
function Game:getSaveFile(saveslot, absolute)
local dir = ""
if absolute then
dir = love.filesystem.getSaveDirectory() .. "/"
if not love.filesystem.exists(dir) then
love.filesystem.createDirectory( "" )
end
end
local filepath = dir .. "save" .. saveslot .. ".save"
return filepath
end
function Game:resetSaves()
for i=1, 3 do
filepath = self:getSaveFile(i, true)
if love.filesystem.exists("save" .. i .. ".save") then
love.filesystem.remove( "save" .. i .. ".save" )
end
end
end
function Game:update(dt)
self.gametime = self.gametime + dt
end
function Game:getTime()
local time = self.gametime
local hours, minute, seconds
seconds = math.floor(self.gametime)
minutes = math.floor(seconds / 60)
hours = math.floor(minutes / 60)
seconds = seconds % 60
minutes = minutes % 60
hours = hours
return seconds, minutes, hours
end
function Game:getTimeString()
local string
local seconds, minutes, hours = self:getTime()
local stringSeconds, stringMinutes, stringHours
if (seconds <= 9) then
stringSeconds = 0 .. seconds
else
stringSeconds = seconds
end
if (minutes <= 9) then
stringMinutes = 0 .. minutes
else
stringMinutes = minutes
end
if (hours <= 9) then
stringHours = 0 .. hours
else
stringHours = hours
end
string = stringHours .. ":" .. stringMinutes .. ":" .. stringSeconds
return string
end
function Game:printTime()
print(self:getTimeString())
end
return Game

View File

@ -0,0 +1,41 @@
local gui = {}
function gui.newBorder(width, height, middlePosition)
local tileset = love.graphics.newImage("assets/gui/borders.png")
local tilequad = {}
local w, h = tileset:getDimensions()
tilequad[1] = love.graphics.newQuad(0, 0, 20, 20, w, h)
tilequad[2] = love.graphics.newQuad(20, 0, 20, 20, w, h)
tilequad[3] = love.graphics.newQuad(40, 0, 20, 20, w, h)
tilequad[4] = love.graphics.newQuad(60, 0, 20, 20, w, h)
local Texture = love.graphics.newCanvas(width, height)
love.graphics.setCanvas(Texture)
utils.graphics.resetColor()
local height = math.ceil(height / 20)
local width = math.ceil(width / 20)
for i=1, width do
if i < middlePosition then
love.graphics.draw(tileset, tilequad[1], (i-1) * 20, 0)
elseif (i == middlePosition) then
love.graphics.draw(tileset, tilequad[2], (i-1) * 20, 0)
else
love.graphics.draw(tileset, tilequad[3], (i-1) * 20, 0)
end
if height > 1 then
for j = 2, height do
love.graphics.draw(tileset, tilequad[4], (i-1) * 20, (j-1) * 20)
end
end
end
love.graphics.setCanvas( )
return Texture
end
return gui

View File

@ -0,0 +1,687 @@
-- binser.lua
--[[
Copyright (c) 2016 Calvin Rose
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local assert = assert
local error = error
local select = select
local pairs = pairs
local getmetatable = getmetatable
local setmetatable = setmetatable
local tonumber = tonumber
local type = type
local loadstring = loadstring or load
local concat = table.concat
local char = string.char
local byte = string.byte
local format = string.format
local sub = string.sub
local dump = string.dump
local floor = math.floor
local frexp = math.frexp
local unpack = unpack or table.unpack
-- Lua 5.3 frexp polyfill
-- From https://github.com/excessive/cpml/blob/master/modules/utils.lua
if not frexp then
local log, abs, floor = math.log, math.abs, math.floor
local log2 = log(2)
frexp = function(x)
if x == 0 then return 0, 0 end
local e = floor(log(abs(x)) / log2 + 1)
return x / 2 ^ e, e
end
end
-- NIL = 202
-- FLOAT = 203
-- TRUE = 204
-- FALSE = 205
-- STRING = 206
-- TABLE = 207
-- REFERENCE = 208
-- CONSTRUCTOR = 209
-- FUNCTION = 210
-- RESOURCE = 211
-- INT64 = 212
local mts = {}
local ids = {}
local serializers = {}
local deserializers = {}
local resources = {}
local resources_by_name = {}
local function pack(...)
return {...}, select("#", ...)
end
local function not_array_index(x, len)
return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x)
end
local function type_check(x, tp, name)
assert(type(x) == tp,
format("Expected parameter %q to be of type %q.", name, tp))
end
local bigIntSupport = false
local isInteger
if math.type then -- Detect Lua 5.3
local mtype = math.type
bigIntSupport = loadstring[[
local char = string.char
return function(n)
local nn = n < 0 and -(n + 1) or n
local b1 = nn // 0x100000000000000
local b2 = nn // 0x1000000000000 % 0x100
local b3 = nn // 0x10000000000 % 0x100
local b4 = nn // 0x100000000 % 0x100
local b5 = nn // 0x1000000 % 0x100
local b6 = nn // 0x10000 % 0x100
local b7 = nn // 0x100 % 0x100
local b8 = nn % 0x100
if n < 0 then
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
end
return char(212, b1, b2, b3, b4, b5, b6, b7, b8)
end]]()
isInteger = function(x)
return mtype(x) == 'integer'
end
else
isInteger = function(x)
return floor(x) == x
end
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number serialization code modified from https://github.com/fperrad/lua-MessagePack
-- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer
local function number_to_str(n)
if isInteger(n) then -- int
if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data
return char(n + 27)
elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data
n = n + 8192
return char(128 + (floor(n / 0x100) % 0x100), n % 0x100)
elseif bigIntSupport then
return bigIntSupport(n)
end
end
local sign = 0
if n < 0.0 then
sign = 0x80
n = -n
end
local m, e = frexp(n) -- mantissa, exponent
if m ~= m then
return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
elseif m == 1/0 then
if sign == 0 then
return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
else
return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
end
end
e = e + 0x3FE
if e < 1 then -- denormalized numbers
m = m * 2 ^ (52 + e)
e = 0
else
m = (m * 2 - 1) * 2 ^ 52
end
return char(203,
sign + floor(e / 0x10),
(e % 0x10) * 0x10 + floor(m / 0x1000000000000),
floor(m / 0x10000000000) % 0x100,
floor(m / 0x100000000) % 0x100,
floor(m / 0x1000000) % 0x100,
floor(m / 0x10000) % 0x100,
floor(m / 0x100) % 0x100,
m % 0x100)
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack
local function number_from_str(str, index)
local b = byte(str, index)
if b < 128 then
return b - 27, index + 1
elseif b < 192 then
return byte(str, index + 1) + 0x100 * (b - 128) - 8192, index + 2
end
local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8)
if b == 212 then
local flip = b1 >= 128
if flip then -- negative
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
end
local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
if flip then
return (-n) - 1, index + 9
else
return n, index + 9
end
end
local sign = b1 > 0x7F and -1 or 1
local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
local n
if e == 0 then
if m == 0 then
n = sign * 0.0
else
n = sign * (m / 2 ^ 52) * 2 ^ -1022
end
elseif e == 0x7FF then
if m == 0 then
n = sign * (1/0)
else
n = 0.0/0.0
end
else
n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF)
end
return n, index + 9
end
local types = {}
types["nil"] = function(x, visited, accum)
accum[#accum + 1] = "\202"
end
function types.number(x, visited, accum)
accum[#accum + 1] = number_to_str(x)
end
function types.boolean(x, visited, accum)
accum[#accum + 1] = x and "\204" or "\205"
end
function types.string(x, visited, accum)
local alen = #accum
if visited[x] then
accum[alen + 1] = "\208"
accum[alen + 2] = number_to_str(visited[x])
else
visited[x] = visited.next
visited.next = visited.next + 1
accum[alen + 1] = "\206"
accum[alen + 2] = number_to_str(#x)
accum[alen + 3] = x
end
end
local function check_custom_type(x, visited, accum)
local res = resources[x]
if res then
accum[#accum + 1] = "\211"
types[type(res)](res, visited, accum)
return true
end
local mt = getmetatable(x)
local id = mt and ids[mt]
if id then
if x == visited.temp then
error("Infinite loop in constructor.")
end
visited.temp = x
accum[#accum + 1] = "\209"
types[type(id)](id, visited, accum)
local args, len = pack(serializers[id](x))
accum[#accum + 1] = number_to_str(len)
for i = 1, len do
local arg = args[i]
types[type(arg)](arg, visited, accum)
end
visited[x] = visited.next
visited.next = visited.next + 1
return true
end
end
function types.userdata(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
error("Cannot serialize this userdata.")
end
end
function types.table(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited.next
visited.next = visited.next + 1
local xlen = #x
accum[#accum + 1] = "\207"
accum[#accum + 1] = number_to_str(xlen)
for i = 1, xlen do
local v = x[i]
types[type(v)](v, visited, accum)
end
local key_count = 0
for k in pairs(x) do
if not_array_index(k, xlen) then
key_count = key_count + 1
end
end
accum[#accum + 1] = number_to_str(key_count)
for k, v in pairs(x) do
if not_array_index(k, xlen) then
types[type(k)](k, visited, accum)
types[type(v)](v, visited, accum)
end
end
end
end
types["function"] = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited.next
visited.next = visited.next + 1
local str = dump(x)
accum[#accum + 1] = "\210"
accum[#accum + 1] = number_to_str(#str)
accum[#accum + 1] = str
end
end
types.cdata = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, #accum) then return end
error("Cannot serialize this cdata.")
end
end
types.thread = function() error("Cannot serialize threads.") end
local function deserialize_value(str, index, visited)
local t = byte(str, index)
if not t then return end
if t < 128 then
return t - 27, index + 1
elseif t < 192 then
return byte(str, index + 1) + 0x100 * (t - 128) - 8192, index + 2
elseif t == 202 then
return nil, index + 1
elseif t == 203 then
return number_from_str(str, index)
elseif t == 204 then
return true, index + 1
elseif t == 205 then
return false, index + 1
elseif t == 206 then
local length, dataindex = deserialize_value(str, index + 1, visited)
local nextindex = dataindex + length
local substr = sub(str, dataindex, nextindex - 1)
visited[#visited + 1] = substr
return substr, nextindex
elseif t == 207 then
local count, nextindex = number_from_str(str, index + 1)
local ret = {}
visited[#visited + 1] = ret
for i = 1, count do
ret[i], nextindex = deserialize_value(str, nextindex, visited)
end
count, nextindex = number_from_str(str, nextindex)
for i = 1, count do
local k, v
k, nextindex = deserialize_value(str, nextindex, visited)
v, nextindex = deserialize_value(str, nextindex, visited)
ret[k] = v
end
return ret, nextindex
elseif t == 208 then
local ref, nextindex = number_from_str(str, index + 1)
return visited[ref], nextindex
elseif t == 209 then
local count
local name, nextindex = deserialize_value(str, index + 1, visited)
count, nextindex = number_from_str(str, nextindex)
local args = {}
for i = 1, count do
args[i], nextindex = deserialize_value(str, nextindex, visited)
end
local ret = deserializers[name](unpack(args))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 210 then
local length, dataindex = deserialize_value(str, index + 1, visited)
local nextindex = dataindex + length
local ret = loadstring(sub(str, dataindex, nextindex - 1))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 211 then
local res, nextindex = deserialize_value(str, index + 1, visited)
return resources_by_name[res], nextindex
elseif t == 212 then
return number_from_str(str, index)
else
error("Could not deserialize type byte " .. t .. ".")
end
end
local function serialize(...)
local visited = {next = 1}
local accum = {}
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
return concat(accum)
end
local function make_file_writer(file)
return setmetatable({}, {
__newindex = function(_, _, v)
file:write(v)
end
})
end
local function serialize_to_file(path, mode, ...)
local file, err = io.open(path, mode)
assert(file, err)
local visited = {next = 1}
local accum = make_file_writer(file)
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
-- flush the writer
file:flush()
file:close()
end
local function writeFile(path, ...)
return serialize_to_file(path, "wb", ...)
end
local function appendFile(path, ...)
return serialize_to_file(path, "ab", ...)
end
local function deserialize(str, index)
assert(type(str) == "string", "Expected string to deserialize.")
local vals = {}
index = index or 1
local visited = {}
local len = 0
local val
while index do
val, index = deserialize_value(str, index, visited)
if index then
len = len + 1
vals[len] = val
end
end
return vals, len
end
local function deserializeN(str, n, index)
assert(type(str) == "string", "Expected string to deserialize.")
n = n or 1
assert(type(n) == "number", "Expected a number for parameter n.")
assert(n > 0 and floor(n) == n, "N must be a poitive integer.")
local vals = {}
index = index or 1
local visited = {}
local len = 0
local val
while index and len < n do
val, index = deserialize_value(str, index, visited)
if index then
len = len + 1
vals[len] = val
end
end
vals[len + 1] = index
return unpack(vals, 1, n + 1)
end
local function readFile(path)
local file, err = io.open(path, "rb")
assert(file, err)
local str = file:read("*all")
file:close()
return deserialize(str)
end
local function default_deserialize(metatable)
return function(...)
local ret = {}
for i = 1, select("#", ...), 2 do
ret[select(i, ...)] = select(i + 1, ...)
end
return setmetatable(ret, metatable)
end
end
local function default_serialize(x)
assert(type(x) == "table",
"Default serialization for custom types only works for tables.")
local args = {}
local len = 0
for k, v in pairs(x) do
args[len + 1], args[len + 2] = k, v
len = len + 2
end
return unpack(args, 1, len)
end
-- Templating
local function normalize_template(template)
local ret = {}
for i = 1, #template do
ret[i] = template[i]
end
local non_array_part = {}
-- The non-array part of the template (nested templates) have to be deterministic, so they are sorted.
-- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used
-- in templates. Looking for way around this.
for k in pairs(template) do
if not_array_index(k, #template) then
non_array_part[#non_array_part + 1] = k
end
end
table.sort(non_array_part)
for i = 1, #non_array_part do
local name = non_array_part[i]
ret[#ret + 1] = {name, normalize_template(template[name])}
end
return ret
end
local function templatepart_serialize(part, argaccum, x, len)
local extras = {}
local extracount = 0
for k, v in pairs(x) do
extras[k] = v
extracount = extracount + 1
end
for i = 1, #part do
extracount = extracount - 1
if type(part[i]) == "table" then
extras[part[i][1]] = nil
len = templatepart_serialize(part[i][2], argaccum, x[part[i][1]], len)
else
extras[part[i]] = nil
len = len + 1
argaccum[len] = x[part[i]]
end
end
if extracount > 0 then
argaccum[len + 1] = extras
else
argaccum[len + 1] = nil
end
return len + 1
end
local function templatepart_deserialize(ret, part, values, vindex)
for i = 1, #part do
local name = part[i]
if type(name) == "table" then
local newret = {}
ret[name[1]] = newret
vindex = templatepart_deserialize(newret, name[2], values, vindex)
else
ret[name] = values[vindex]
vindex = vindex + 1
end
end
local extras = values[vindex]
if extras then
for k, v in pairs(extras) do
ret[k] = v
end
end
return vindex + 1
end
local function template_serializer_and_deserializer(metatable, template)
return function(x)
argaccum = {}
local len = templatepart_serialize(template, argaccum, x, 0)
return unpack(argaccum, 1, len)
end, function(...)
local ret = {}
local len = select("#", ...)
local args = {...}
templatepart_deserialize(ret, template, args, 1)
return setmetatable(ret, metatable)
end
end
local function register(metatable, name, serialize, deserialize)
name = name or metatable.name
serialize = serialize or metatable._serialize
deserialize = deserialize or metatable._deserialize
if not serialize then
if metatable._template then
local t = normalize_template(metatable._template)
serialize, deserialize = template_serializer_and_deserializer(metatable, t)
elseif not deserialize then
serialize = default_serialize
deserialize = default_deserialize(metatable)
else
serialize = metatable
end
end
type_check(metatable, "table", "metatable")
type_check(name, "string", "name")
type_check(serialize, "function", "serialize")
type_check(deserialize, "function", "deserialize")
assert(not ids[metatable], "Metatable already registered.")
assert(not mts[name], ("Name %q already registered."):format(name))
mts[name] = metatable
ids[metatable] = name
serializers[name] = serialize
deserializers[name] = deserialize
return metatable
end
local function unregister(item)
local name, metatable
if type(item) == "string" then -- assume name
name, metatable = item, mts[item]
else -- assume metatable
name, metatable = ids[item], item
end
type_check(name, "string", "name")
type_check(metatable, "table", "metatable")
mts[name] = nil
ids[metatable] = nil
serializers[name] = nil
deserializers[name] = nil
return metatable
end
local function registerClass(class, name)
name = name or class.name
if class.__instanceDict then -- middleclass
register(class.__instanceDict, name)
else -- assume 30log or similar library
register(class, name)
end
return class
end
local function registerResource(resource, name)
type_check(name, "string", "name")
assert(not resources[resource],
"Resource already registered.")
assert(not resources_by_name[name],
format("Resource %q already exists.", name))
resources_by_name[name] = resource
resources[resource] = name
return resource
end
local function unregisterResource(name)
type_check(name, "string", "name")
assert(resources_by_name[name], format("Resource %q does not exist.", name))
local resource = resources_by_name[name]
resources_by_name[name] = nil
resources[resource] = nil
return resource
end
return {
-- aliases
s = serialize,
d = deserialize,
dn = deserializeN,
r = readFile,
w = writeFile,
a = appendFile,
serialize = serialize,
deserialize = deserialize,
deserializeN = deserializeN,
readFile = readFile,
writeFile = writeFile,
appendFile = appendFile,
register = register,
unregister = unregister,
registerResource = registerResource,
unregisterResource = unregisterResource,
registerClass = registerClass
}

View File

@ -0,0 +1,68 @@
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.
--
local Object = {}
Object.__index = Object
function Object:new()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:implement(...)
for _, cls in pairs({...}) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__tostring()
return "Object"
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

View File

@ -0,0 +1,99 @@
--[[
CScreen v1.3 by CodeNMore
A simple way to make resolution-independent Love2D games
Tested for LOVE 0.10.1
See: https://github.com/CodeNMore/CScreen
Zlib License:
Copyright (c) 2016 CodeNMore
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in
a product, an acknowledgment in the product documentation would be appreciated
but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
--]]
local CScreen = {}
local rx, ry, ctr = 800, 600, true
local rxv, ryv, fsv, fsvr = 800, 600, 1.0, 1.0
local tx, ty, rwf, rhf = 0, 0, 800, 600
local cr, cg, cb, ca = 0, 0, 0, 255
-- Initializes CScreen with the initial size values
function CScreen.init(tw, th, cntr)
rx = tw or 800
ry = th or 600
ctr = cntr or false
CScreen.update(love.graphics.getWidth(), love.graphics.getHeight())
end
-- Draws letterbox borders
function CScreen.cease()
if ctr then
local pr, pg, pb, pa = love.graphics.getColor()
love.graphics.setColor(cr, cg, cb, ca)
love.graphics.scale(fsvr, fsvr)
if tx ~= 0 then
love.graphics.rectangle("fill", -tx, 0, tx, rhf)
love.graphics.rectangle("fill", rxv, 0, tx, rhf)
elseif ty ~= 0 then
love.graphics.rectangle("fill", 0, -ty, rwf, ty)
love.graphics.rectangle("fill", 0, ryv, rwf, ty)
end
love.graphics.setColor(pr, pg, pb, pa)
end
end
-- Scales and centers all graphics properly
function CScreen.apply()
if ctr then
love.graphics.translate(tx, ty)
end
love.graphics.scale(fsv, fsv)
end
-- Updates CScreen when the window size changes
function CScreen.update(w, h)
local sx = w / rx
local sy = h / ry
fsv = math.min(sx, sy)
fsvr = 1 / fsv
-- Centering
if ctr and fsv == sx then -- Vertically
tx = 0
ty = (h / 2) - (ry * fsv / 2)
elseif ctr and fsv == sy then -- Horizontally
ty = 0
tx = (w / 2) - (rx * fsv / 2)
end
-- Variable sets
rwf = w
rhf = h
rxv = rx * fsv
ryv = ry * fsv
end
-- Convert from window coordinates to target coordinates
function CScreen.project(x, y)
return math.floor((x - tx) / fsv), math.floor((y - ty) / fsv)
end
-- Change letterbox color
function CScreen.setColor(r, g, b, a)
cr = r
cg = g
cb = b
ca = a
end
-- Return the table for use
return CScreen

View File

@ -0,0 +1,737 @@
--
-- lovebird
--
-- Copyright (c) 2017 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local socket = require "socket"
local lovebird = { _version = "0.4.3" }
lovebird.loadstring = loadstring or load
lovebird.inited = false
lovebird.host = "*"
lovebird.buffer = ""
lovebird.lines = {}
lovebird.connections = {}
lovebird.pages = {}
lovebird.wrapprint = true
lovebird.timestamp = true
lovebird.allowhtml = false
lovebird.echoinput = true
lovebird.port = 8000
lovebird.whitelist = { "127.0.0.1" }
lovebird.maxlines = 200
lovebird.updateinterval = .5
lovebird.pages["index"] = [[
<?lua
-- Handle console input
if req.parsedbody.input then
local str = req.parsedbody.input
if lovebird.echoinput then
lovebird.pushline({ type = 'input', str = str })
end
if str:find("^=") then
str = "print(" .. str:sub(2) .. ")"
end
xpcall(function() assert(lovebird.loadstring(str, "input"))() end,
lovebird.onerror)
end
?>
<!doctype html>
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
<meta charset="utf-8">
<title>lovebird</title>
<style>
body {
margin: 0px;
font-size: 14px;
font-family: helvetica, verdana, sans;
background: #FFFFFF;
}
form {
margin-bottom: 0px;
}
.timestamp {
color: #909090;
padding-right: 4px;
}
.repeatcount {
color: #F0F0F0;
background: #505050;
font-size: 11px;
font-weight: bold;
text-align: center;
padding-left: 4px;
padding-right: 4px;
padding-top: 0px;
padding-bottom: 0px;
border-radius: 7px;
display: inline-block;
}
.errormarker {
color: #F0F0F0;
background: #8E0000;
font-size: 11px;
font-weight: bold;
text-align: center;
border-radius: 8px;
width: 17px;
padding-top: 0px;
padding-bottom: 0px;
display: inline-block;
}
.greybordered {
margin: 12px;
background: #F0F0F0;
border: 1px solid #E0E0E0;
border-radius: 3px;
}
.inputline {
font-family: mono, courier;
font-size: 13px;
color: #606060;
}
.inputline:before {
content: '\00B7\00B7\00B7';
padding-right: 5px;
}
.errorline {
color: #8E0000;
}
#header {
background: #101010;
height: 25px;
color: #F0F0F0;
padding: 9px
}
#title {
float: left;
font-size: 20px;
}
#title a {
color: #F0F0F0;
text-decoration: none;
}
#title a:hover {
color: #FFFFFF;
}
#version {
font-size: 10px;
}
#status {
float: right;
font-size: 14px;
padding-top: 4px;
}
#main a {
color: #000000;
text-decoration: none;
background: #E0E0E0;
border: 1px solid #D0D0D0;
border-radius: 3px;
padding-left: 2px;
padding-right: 2px;
display: inline-block;
}
#main a:hover {
background: #D0D0D0;
border: 1px solid #C0C0C0;
}
#console {
position: absolute;
top: 40px; bottom: 0px; left: 0px; right: 312px;
}
#input {
position: absolute;
margin: 10px;
bottom: 0px; left: 0px; right: 0px;
}
#inputbox {
width: 100%;
font-family: mono, courier;
font-size: 13px;
}
#output {
overflow-y: scroll;
position: absolute;
margin: 10px;
line-height: 17px;
top: 0px; bottom: 36px; left: 0px; right: 0px;
}
#env {
position: absolute;
top: 40px; bottom: 0px; right: 0px;
width: 300px;
}
#envheader {
padding: 5px;
background: #E0E0E0;
}
#envvars {
position: absolute;
left: 0px; right: 0px; top: 25px; bottom: 0px;
margin: 10px;
overflow-y: scroll;
font-size: 12px;
}
</style>
</head>
<body>
<div id="header">
<div id="title">
<a href="https://github.com/rxi/lovebird">lovebird</a>
<span id="version"><?lua echo(lovebird._version) ?></span>
</div>
<div id="status"></div>
</div>
<div id="main">
<div id="console" class="greybordered">
<div id="output"> <?lua echo(lovebird.buffer) ?> </div>
<div id="input">
<form method="post"
onkeydown="return onInputKeyDown(event);"
onsubmit="onInputSubmit(); return false;">
<input id="inputbox" name="input" type="text"
autocomplete="off"></input>
</form>
</div>
</div>
<div id="env" class="greybordered">
<div id="envheader"></div>
<div id="envvars"></div>
</div>
</div>
<script>
document.getElementById("inputbox").focus();
var changeFavicon = function(href) {
var old = document.getElementById("favicon");
if (old) document.head.removeChild(old);
var link = document.createElement("link");
link.id = "favicon";
link.rel = "shortcut icon";
link.href = href;
document.head.appendChild(link);
}
var truncate = function(str, len) {
if (str.length <= len) return str;
return str.substring(0, len - 3) + "...";
}
var geturl = function(url, onComplete, onFail) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState != 4) return;
if (req.status == 200) {
if (onComplete) onComplete(req.responseText);
} else {
if (onFail) onFail(req.responseText);
}
}
url += (url.indexOf("?") > -1 ? "&_=" : "?_=") + Math.random();
req.open("GET", url, true);
req.send();
}
var divContentCache = {}
var updateDivContent = function(id, content) {
if (divContentCache[id] != content) {
document.getElementById(id).innerHTML = content;
divContentCache[id] = content
return true;
}
return false;
}
var onInputSubmit = function() {
var b = document.getElementById("inputbox");
var req = new XMLHttpRequest();
req.open("POST", "/", true);
req.send("input=" + encodeURIComponent(b.value));
/* Do input history */
if (b.value && inputHistory[0] != b.value) {
inputHistory.unshift(b.value);
}
inputHistory.index = -1;
/* Reset */
b.value = "";
refreshOutput();
}
/* Input box history */
var inputHistory = [];
inputHistory.index = 0;
var onInputKeyDown = function(e) {
var key = e.which || e.keyCode;
if (key != 38 && key != 40) return true;
var b = document.getElementById("inputbox");
if (key == 38 && inputHistory.index < inputHistory.length - 1) {
/* Up key */
inputHistory.index++;
}
if (key == 40 && inputHistory.index >= 0) {
/* Down key */
inputHistory.index--;
}
b.value = inputHistory[inputHistory.index] || "";
b.selectionStart = b.value.length;
return false;
}
/* Output buffer and status */
var refreshOutput = function() {
geturl("/buffer", function(text) {
updateDivContent("status", "connected &#9679;");
if (updateDivContent("output", text)) {
var div = document.getElementById("output");
div.scrollTop = div.scrollHeight;
}
/* Update favicon */
changeFavicon("data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAP1BMVEUAAAAAAAAAAAD////19fUO"+
"Dg7v7+/h4eGzs7MlJSUeHh7n5+fY2NjJycnGxsa3t7eioqKfn5+QkJCHh4d+fn7zU+b5AAAAAnRS"+
"TlPlAFWaypEAAABRSURBVBjTfc9HDoAwDERRQ+w0ern/WQkZaUBC4e/mrWzppH9VJjbjZg1Ii2rM"+
"DyR1JZ8J0dVWggIGggcEwgbYCRbuPRqgyjHNpzUP+39GPu9fgloC5L9DO0sAAAAASUVORK5CYII="
);
},
function(text) {
updateDivContent("status", "disconnected &#9675;");
/* Update favicon */
changeFavicon("data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAAAAAAAADZ2dm4uLgM"+
"DAz29vbz8/Pv7+/h4eHIyMiwsLBtbW0lJSUeHh4QEBDn5+fS0tLDw8O0tLSioqKfn5+QkJCHh4d+"+
"fn5ycnJmZmZgYGBXV1dLS0tFRUUGBgZ0He44AAAAAnRSTlPlAFWaypEAAABeSURBVBjTfY9HDoAw"+
"DAQD6Z3ey/9/iXMxkVDYw0g7F3tJReosUKHnwY4pCM+EtOEVXrb7wVRA0dMbaAcUwiVeDQq1Jp4a"+
"xUg5kE0ooqZu68Di2Tgbs/DiY/9jyGf+AyFKBAK7KD2TAAAAAElFTkSuQmCC"
);
});
}
setInterval(refreshOutput,
<?lua echo(lovebird.updateinterval) ?> * 1000);
/* Environment variable view */
var envPath = "";
var refreshEnv = function() {
geturl("/env.json?p=" + envPath, function(text) {
var json = eval("(" + text + ")");
/* Header */
var html = "<a href='#' onclick=\"setEnvPath('')\">env</a>";
var acc = "";
var p = json.path != "" ? json.path.split(".") : [];
for (var i = 0; i < p.length; i++) {
acc += "." + p[i];
html += " <a href='#' onclick=\"setEnvPath('" + acc + "')\">" +
truncate(p[i], 10) + "</a>";
}
updateDivContent("envheader", html);
/* Handle invalid table path */
if (!json.valid) {
updateDivContent("envvars", "Bad path");
return;
}
/* Variables */
var html = "<table>";
for (var i = 0; json.vars[i]; i++) {
var x = json.vars[i];
var fullpath = (json.path + "." + x.key).replace(/^\./, "");
var k = truncate(x.key, 15);
if (x.type == "table") {
k = "<a href='#' onclick=\"setEnvPath('" + fullpath + "')\">" +
k + "</a>";
}
var v = "<a href='#' onclick=\"insertVar('" +
fullpath.replace(/\.(-?[0-9]+)/g, "[$1]") +
"');\">" + x.value + "</a>"
html += "<tr><td>" + k + "</td><td>" + v + "</td></tr>";
}
html += "</table>";
updateDivContent("envvars", html);
});
}
var setEnvPath = function(p) {
envPath = p;
refreshEnv();
}
var insertVar = function(p) {
var b = document.getElementById("inputbox");
b.value += p;
b.focus();
}
setInterval(refreshEnv, <?lua echo(lovebird.updateinterval) ?> * 1000);
</script>
</body>
</html>
]]
lovebird.pages["buffer"] = [[ <?lua echo(lovebird.buffer) ?> ]]
lovebird.pages["env.json"] = [[
<?lua
local t = _G
local p = req.parsedurl.query.p or ""
p = p:gsub("%.+", "."):match("^[%.]*(.*)[%.]*$")
if p ~= "" then
for x in p:gmatch("[^%.]+") do
t = t[x] or t[tonumber(x)]
-- Return early if path does not exist
if type(t) ~= "table" then
echo('{ "valid": false, "path": ' .. string.format("%q", p) .. ' }')
return
end
end
end
?>
{
"valid": true,
"path": "<?lua echo(p) ?>",
"vars": [
<?lua
local keys = {}
for k in pairs(t) do
if type(k) == "number" or type(k) == "string" then
table.insert(keys, k)
end
end
table.sort(keys, lovebird.compare)
for _, k in pairs(keys) do
local v = t[k]
?>
{
"key": "<?lua echo(k) ?>",
"value": <?lua echo(
string.format("%q",
lovebird.truncate(
lovebird.htmlescape(
tostring(v)), 26))) ?>,
"type": "<?lua echo(type(v)) ?>",
},
<?lua end ?>
]
}
]]
function lovebird.init()
-- Init server
lovebird.server = assert(socket.bind(lovebird.host, lovebird.port))
lovebird.addr, lovebird.port = lovebird.server:getsockname()
lovebird.server:settimeout(0)
-- Wrap print
lovebird.origprint = print
if lovebird.wrapprint then
local oldprint = print
print = function(...)
oldprint(...)
lovebird.print(...)
end
end
-- Compile page templates
for k, page in pairs(lovebird.pages) do
lovebird.pages[k] = lovebird.template(page, "lovebird, req",
"pages." .. k)
end
lovebird.inited = true
end
function lovebird.template(str, params, chunkname)
params = params and ("," .. params) or ""
local f = function(x) return string.format(" echo(%q)", x) end
str = ("?>"..str.."<?lua"):gsub("%?>(.-)<%?lua", f)
str = "local echo " .. params .. " = ..." .. str
local fn = assert(lovebird.loadstring(str, chunkname))
return function(...)
local output = {}
local echo = function(str) table.insert(output, str) end
fn(echo, ...)
return table.concat(lovebird.map(output, tostring))
end
end
function lovebird.map(t, fn)
local res = {}
for k, v in pairs(t) do res[k] = fn(v) end
return res
end
function lovebird.trace(...)
local str = "[lovebird] " .. table.concat(lovebird.map({...}, tostring), " ")
print(str)
if not lovebird.wrapprint then lovebird.print(str) end
end
function lovebird.unescape(str)
local f = function(x) return string.char(tonumber("0x"..x)) end
return (str:gsub("%+", " "):gsub("%%(..)", f))
end
function lovebird.parseurl(url)
local res = {}
res.path, res.search = url:match("/([^%?]*)%??(.*)")
res.query = {}
for k, v in res.search:gmatch("([^&^?]-)=([^&^#]*)") do
res.query[k] = lovebird.unescape(v)
end
return res
end
local htmlescapemap = {
["<"] = "&lt;",
["&"] = "&amp;",
['"'] = "&quot;",
["'"] = "&#039;",
}
function lovebird.htmlescape(str)
return ( str:gsub("[<&\"']", htmlescapemap) )
end
function lovebird.truncate(str, len)
if #str <= len then
return str
end
return str:sub(1, len - 3) .. "..."
end
function lovebird.compare(a, b)
local na, nb = tonumber(a), tonumber(b)
if na then
if nb then return na < nb end
return false
elseif nb then
return true
end
return tostring(a) < tostring(b)
end
function lovebird.checkwhitelist(addr)
if lovebird.whitelist == nil then return true end
for _, a in pairs(lovebird.whitelist) do
local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$"
if addr:match(ptn) then return true end
end
return false
end
function lovebird.clear()
lovebird.lines = {}
lovebird.buffer = ""
end
function lovebird.pushline(line)
line.time = os.time()
line.count = 1
table.insert(lovebird.lines, line)
if #lovebird.lines > lovebird.maxlines then
table.remove(lovebird.lines, 1)
end
lovebird.recalcbuffer()
end
function lovebird.recalcbuffer()
local function doline(line)
local str = line.str
if not lovebird.allowhtml then
str = lovebird.htmlescape(line.str):gsub("\n", "<br>")
end
if line.type == "input" then
str = '<span class="inputline">' .. str .. '</span>'
else
if line.type == "error" then
str = '<span class="errormarker">!</span> ' .. str
str = '<span class="errorline">' .. str .. '</span>'
end
if line.count > 1 then
str = '<span class="repeatcount">' .. line.count .. '</span> ' .. str
end
if lovebird.timestamp then
str = os.date('<span class="timestamp">%H:%M:%S</span> ', line.time) ..
str
end
end
return str
end
lovebird.buffer = table.concat(lovebird.map(lovebird.lines, doline), "<br>")
end
function lovebird.print(...)
local t = {}
for i = 1, select("#", ...) do
table.insert(t, tostring(select(i, ...)))
end
local str = table.concat(t, " ")
local last = lovebird.lines[#lovebird.lines]
if last and str == last.str then
-- Update last line if this line is a duplicate of it
last.time = os.time()
last.count = last.count + 1
lovebird.recalcbuffer()
else
-- Create new line
lovebird.pushline({ type = "output", str = str })
end
end
function lovebird.onerror(err)
lovebird.pushline({ type = "error", str = err })
if lovebird.wrapprint then
lovebird.origprint("[lovebird] ERROR: " .. err)
end
end
function lovebird.onrequest(req, client)
local page = req.parsedurl.path
page = page ~= "" and page or "index"
-- Handle "page not found"
if not lovebird.pages[page] then
return "HTTP/1.1 404\r\nContent-Length: 8\r\n\r\nBad page"
end
-- Handle page
local str
xpcall(function()
local data = lovebird.pages[page](lovebird, req)
local contenttype = "text/html"
if string.match(page, "%.json$") then
contenttype = "application/json"
end
str = "HTTP/1.1 200 OK\r\n" ..
"Content-Type: " .. contenttype .. "\r\n" ..
"Content-Length: " .. #data .. "\r\n" ..
"\r\n" .. data
end, lovebird.onerror)
return str
end
function lovebird.receive(client, pattern)
while 1 do
local data, msg = client:receive(pattern)
if not data then
if msg == "timeout" then
-- Wait for more data
coroutine.yield(true)
else
-- Disconnected -- yielding nil means we're done
coroutine.yield(nil)
end
else
return data
end
end
end
function lovebird.send(client, data)
local idx = 1
while idx < #data do
local res, msg = client:send(data, idx)
if not res and msg == "closed" then
-- Handle disconnect
coroutine.yield(nil)
else
idx = idx + res
coroutine.yield(true)
end
end
end
function lovebird.onconnect(client)
-- Create request table
local requestptn = "(%S*)%s*(%S*)%s*(%S*)"
local req = {}
req.socket = client
req.addr, req.port = client:getsockname()
req.request = lovebird.receive(client, "*l")
req.method, req.url, req.proto = req.request:match(requestptn)
req.headers = {}
while 1 do
local line, msg = lovebird.receive(client, "*l")
if not line or #line == 0 then break end
local k, v = line:match("(.-):%s*(.*)$")
req.headers[k] = v
end
if req.headers["Content-Length"] then
req.body = lovebird.receive(client, req.headers["Content-Length"])
end
-- Parse body
req.parsedbody = {}
if req.body then
for k, v in req.body:gmatch("([^&]-)=([^&^#]*)") do
req.parsedbody[k] = lovebird.unescape(v)
end
end
-- Parse request line's url
req.parsedurl = lovebird.parseurl(req.url)
-- Handle request; get data to send and send
local data = lovebird.onrequest(req)
lovebird.send(client, data)
-- Clear up
client:close()
end
function lovebird.update()
if not lovebird.inited then lovebird.init() end
-- Handle new connections
while 1 do
-- Accept new connections
local client = lovebird.server:accept()
if not client then break end
client:settimeout(0)
local addr = client:getsockname()
if lovebird.checkwhitelist(addr) then
-- Connection okay -- create and add coroutine to set
local conn = coroutine.wrap(function()
xpcall(function() lovebird.onconnect(client) end, function() end)
end)
lovebird.connections[conn] = true
else
-- Reject connection not on whitelist
lovebird.trace("got non-whitelisted connection attempt: ", addr)
client:close()
end
end
-- Handle existing connections
for conn in pairs(lovebird.connections) do
-- Resume coroutine, remove if it has finished
local status = conn()
if status == nil then
lovebird.connections[conn] = nil
end
end
end
return lovebird

View File

@ -0,0 +1,16 @@
local Filesystem = {}
function Filesystem.exists(filepath)
local info = love.filesystem.getInfo( filepath )
local exists = false
if (info == nil) then
exists = false
else
exists = true
end
return exists
end
return Filesystem

View File

@ -0,0 +1,64 @@
local Graphics = {}
function Graphics.resetColor()
love.graphics.setColor(1,1,1,1)
end
function Graphics.box(x, y, w, h)
local x = math.floor(x)
local y = math.floor(y)
local w = math.floor(w)
local h = math.floor(h)
local a = a or 1
local r, g, b, a = love.graphics.getColor( )
love.graphics.setColor(r, g, b, 0.3 * a)
love.graphics.rectangle("fill", x, y, w, h)
love.graphics.setColor(r, g, b, a)
love.graphics.rectangle("line", x, y, w, h)
end
function Graphics.print(text, x, y, align, r, sx, sy, ox, oy, kx, ky)
local width
local font = love.graphics.getFont()
width = font:getWidth(text)
if align == "center" then
width = (width/2)
elseif align == "right" then
width = width
else
width = 0
end
love.graphics.print(text, x - (width), y, r, sx, sy, ox, oy, kx, ky)
end
function Graphics.printWithSpacing(text, spacing, align, x, y, r, sx, sy, ox, oy, kx, ky)
-- DO NOT USE THIS FUNCTION IN A "UPDATE" FUNCTION !
-- it's pretty heavy to use as it use a loop to get every character in a text
local font = love.graphics.getFont()
local xx = 0
local lenght = string.len(text)
local basewidth = font:getWidth(text)
local width = basewidth + (spacing * lenght)
if align == "center" then
width = (width/2)
elseif align == "right" then
width = width
else
width = 0
end
for i=1, lenght do
local char = string.sub(text, i, i)
pos = math.floor(x + xx - width)
love.graphics.print(char, pos, y)
xx = xx + font:getWidth(char) + spacing
end
end
return Graphics

View File

@ -0,0 +1,7 @@
local cwd = (...):gsub('%.init$', '') .. "."
return {
math = require(cwd .. "math"),
graphics = require(cwd .. "graphics"),
filesystem = require(cwd .. "filesystem")
}

View File

@ -0,0 +1,88 @@
local Math = {}
function Math.sign(x)
if (x < 0) then
return -1
elseif (x > 0) then
return 1
else
return 0
end
end
function Math.round(num)
return math.floor(num + 0.5)
end
function Math.vector(x1, y1, x2, y2)
local vecx, vecy
vecx = x2 - x1
vexy = y2 - y1
return vecx, vecy
end
function Math.getMiddlePoint(x1, y1, x2, y2)
local newx, newy, vecx, vecy
vecx = math.max(x1, x2) - math.min(x1, x2)
vecy = math.max(y1, y2) - math.min(y1, y2)
newx = math.min(x1, x2) + (vecx / 2)
newy = math.min(y1, y2) + (vecy / 2)
return newx, newy
end
function Math.pointDistance(x1, y1, x2, y2)
local vecx, vecy
vecx = math.max(x1, x2) - math.min(x1, x2)
vexy = math.max(y1, y2) - math.min(y1, y2)
return math.sqrt(vecx^2 + vecy^2)
end
function Math.pointDirection(x1,y1,x2,y2)
local vecx, vecy, angle
vecy = y2 - y1
vecx = x2 - x1
angle = math.atan2(vecy, vecx)
return angle
end
function Math.numberToString(x, length)
local length = length or 1
local string = ""
local x = x
if (x >= math.pow(10, length)) then
x = unitsNumber*10 - 1
string = string .. x
else
for i=1, (length-1) do
if (x < math.pow(10, length-i)) then
string = string .. "0"
end
end
string = string .. x
end
return string
end
function Math.floorCoord(x, y)
return math.floor(x), math.floor(y)
end
function Math.pixeliseCoord(x, y, factor)
x, y = Math.floorCoord(x / factor, y / factor)
x = x * factor
y = y * factor
return x, y
end
return Math

View File

@ -0,0 +1,53 @@
-- main.lua :: the core file of the game, will load main libs and core system
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
utils = require "libs.loveutils"
Object = require "libs.classic"
Core = require "core"
Game = require "game"
scenes = require "scenes"
function love.load()
core = Core()
game = Game()
scenes.title()
end
function love.update(dt)
core:update(dt)
game:update(dt)
end
function love.draw()
core:draw()
end
function love.mousemoved(x, y, dx, dy)
core:mousemoved(x, y, dx, dy)
end
function love.mousepressed( x, y, button, istouch )
core:mousemoved(x, y, button, istouch)
end

View File

@ -0,0 +1,4 @@
return {
test = require "scenes.test_scene",
title = require "scenes.titlescreen",
}

View File

@ -0,0 +1,47 @@
-- scenes/test :: a basic test scene
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Scene = require "core.modules.scenes"
local TestScene = Scene:extend()
function TestScene:new()
TestScene.super.new(self)
self.i = 0
self:register()
end
function TestScene:update(dt)
self.i = self.i + dt
end
function TestScene:draw()
love.graphics.setColor(0, 0, 1, 1)
love.graphics.rectangle("fill", 0, 0, 424, 240)
love.graphics.setColor(0, 0, 0, 1)
love.graphics.print(self.i .. " ; " .. self.mouse.x .. ":" .. self.mouse.y, 16, 16)
end
return TestScene

View File

@ -0,0 +1,55 @@
-- scenes/test :: a basic test scene
--[[
Copyright © 2019 Kazhnuz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
local Scene = require "core.modules.scenes"
local TitleScreen = Scene:extend()
local gui = require "game.modules.gui"
function TitleScreen:new()
TitleScreen.super.new(self)
self.borders = gui.newBorder(424, 30, 8)
self.assets:addImage("background", "assets/backgrounds/titlescreen.png")
self.assets:addImage("sonic", "assets/artworks/titlescreen_sonic.png")
self.assets:addImage("logo", "assets/artworks/logo.png")
self:register()
end
function TitleScreen:update(dt)
end
function TitleScreen:draw()
utils.graphics.resetColor()
self.assets:drawImage("background", 0, 0)
love.graphics.draw(self.borders, 0, 25, 0, 1, -1)
love.graphics.draw(self.borders, 424, 215, 0, -1, 1)
self.assets:drawImage("sonic", 90, 128, 0, 1, 1, 92, 106)
self.assets:drawImage("logo", 290, 60, 0, 1, 1, 150, 71)
end
return TitleScreen